customização para editar ura e filas

pull/21/head
adriano 2023-07-20 12:30:26 -03:00
parent 92f7d8b4db
commit 21ce9e1825
13 changed files with 1563 additions and 1407 deletions

View File

@ -7,9 +7,9 @@ import UpdateSettingService from "../services/SettingServices/UpdateSettingServi
import ListSettingsService from "../services/SettingServices/ListSettingsService"; import ListSettingsService from "../services/SettingServices/ListSettingsService";
export const index = async (req: Request, res: Response): Promise<Response> => { export const index = async (req: Request, res: Response): Promise<Response> => {
if (req.user.profile !== "master") { // if (req.user.profile !== "master") {
throw new AppError("ERR_NO_PERMISSION", 403); // throw new AppError("ERR_NO_PERMISSION", 403);
} // }
const settings = await ListSettingsService(); const settings = await ListSettingsService();

View File

@ -0,0 +1,22 @@
import { QueryInterface } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.bulkInsert(
"Settings",
[
{
key: "editURA",
value: "enabled",
createdAt: new Date(),
updatedAt: new Date()
}
],
{}
);
},
down: (queryInterface: QueryInterface) => {
return queryInterface.bulkDelete("Settings", {});
}
};

View File

@ -0,0 +1,22 @@
import { QueryInterface } from "sequelize";
module.exports = {
up: (queryInterface: QueryInterface) => {
return queryInterface.bulkInsert(
"Settings",
[
{
key: "editQueue",
value: "enabled",
createdAt: new Date(),
updatedAt: new Date()
}
],
{}
);
},
down: (queryInterface: QueryInterface) => {
return queryInterface.bulkDelete("Settings", {});
}
};

View File

@ -5,9 +5,7 @@ import * as SettingController from "../controllers/SettingController";
const settingRoutes = Router(); const settingRoutes = Router();
settingRoutes.get("/settings", SettingController.index);
settingRoutes.get("/settings", isAuth, SettingController.index);
// routes.get("/settings/:settingKey", isAuth, SettingsController.show); // routes.get("/settings/:settingKey", isAuth, SettingsController.show);

View File

@ -1,8 +1,14 @@
import StatusChatEnd from "../../models/StatusChatEnd"; import StatusChatEnd from "../../models/StatusChatEnd";
import AppError from "../../errors/AppError"; import AppError from "../../errors/AppError";
const ShowStatusChatEndService = async (id: string | number): Promise<StatusChatEnd> => { const ShowStatusChatEndService = async (
const status = await StatusChatEnd.findByPk(id, { attributes: ['id', 'name'], }); id: string | number
): Promise<StatusChatEnd> => {
const status = await StatusChatEnd.findByPk(id, {
attributes: ["id", "name"]
});
console.log(`---------------> statusChatEnd id: ${id}`);
if (!status) { if (!status) {
throw new AppError("ERR_NO_STATUS_FOUND", 404); throw new AppError("ERR_NO_STATUS_FOUND", 404);

View File

@ -1,57 +1,54 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from 'react'
import Routes from "./routes"; import Routes from './routes'
import "react-toastify/dist/ReactToastify.css"; import 'react-toastify/dist/ReactToastify.css'
import { createTheme, ThemeProvider } from "@material-ui/core/styles"; import { createTheme, ThemeProvider } from '@material-ui/core/styles'
import { ptBR } from "@material-ui/core/locale"; import { ptBR } from '@material-ui/core/locale'
import { TabTicketProvider } from "../src/context/TabTicketHeaderOption/TabTicketHeaderOption"; import { TabTicketProvider } from '../src/context/TabTicketHeaderOption/TabTicketHeaderOption'
const App = () => { const App = () => {
const [locale, setLocale] = useState(); const [locale, setLocale] = useState()
const theme = createTheme( const theme = createTheme(
{ {
scrollbarStyles: { scrollbarStyles: {
"&::-webkit-scrollbar": { '&::-webkit-scrollbar': {
width: "8px", width: '8px',
height: "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)',
backgroundColor: "#e8e8e8", backgroundColor: '#e8e8e8',
}, },
}, },
palette: { palette: {
//primary: { main: "#2576d2" }, //primary: { main: "#2576d2" },
primary: { main: "#ec5114" }, primary: { main: '#ec5114' },
}, },
}, },
locale locale
); )
useEffect(() => { useEffect(() => {
const i18nlocale = localStorage.getItem("i18nextLng"); const i18nlocale = localStorage.getItem('i18nextLng')
const browserLocale = const browserLocale =
i18nlocale.substring(0, 2) + i18nlocale.substring(3, 5); i18nlocale.substring(0, 2) + i18nlocale.substring(3, 5)
if (browserLocale === "ptBR") { if (browserLocale === 'ptBR') {
setLocale(ptBR); setLocale(ptBR)
} }
}, []); }, [])
return ( return (
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
{/*TabTicketProvider Context to manipulate the entire state of selected option from user click on tickets options header */} {/*TabTicketProvider Context to manipulate the entire state of selected option from user click on tickets options header */}
<TabTicketProvider> <TabTicketProvider>
<Routes /> <Routes />
</TabTicketProvider> </TabTicketProvider>
</ThemeProvider> </ThemeProvider>
); )
}; }
export default App; export default App

View File

@ -1,10 +1,13 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useContext } from 'react'
import * as Yup from "yup"; import * as Yup from 'yup'
import { Formik, Form, Field } from "formik"; import { Formik, Form, Field } from 'formik'
import { toast } from "react-toastify"; import { toast } from 'react-toastify'
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'
import { AuthContext } from '../../context/Auth/AuthContext'
import { Can } from '../../components/Can'
import { import {
Dialog, Dialog,
@ -16,114 +19,119 @@ import {
TextField, TextField,
Switch, Switch,
FormControlLabel, FormControlLabel,
} from "@material-ui/core"; } from '@material-ui/core'
import api from "../../services/api"; import api from '../../services/api'
import { i18n } from "../../translate/i18n"; import { i18n } from '../../translate/i18n'
import toastError from "../../errors/toastError"; import toastError from '../../errors/toastError'
import QueueSelect from "../QueueSelect"; import QueueSelect from '../QueueSelect'
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles((theme) => ({
root: { root: {
display: "flex", display: 'flex',
flexWrap: "wrap", flexWrap: 'wrap',
}, },
multFieldLine: { multFieldLine: {
display: "flex", display: 'flex',
"& > *:not(:last-child)": { '& > *:not(:last-child)': {
marginRight: theme.spacing(1), marginRight: theme.spacing(1),
}, },
}, },
btnWrapper: { btnWrapper: {
position: "relative", position: 'relative',
}, },
buttonProgress: { buttonProgress: {
color: green[500], color: green[500],
position: "absolute", position: 'absolute',
top: "50%", top: '50%',
left: "50%", left: '50%',
marginTop: -12, marginTop: -12,
marginLeft: -12, marginLeft: -12,
}, },
})); }))
const SessionSchema = Yup.object().shape({ const SessionSchema = Yup.object().shape({
name: Yup.string() name: Yup.string()
.min(2, "Too Short!") .min(2, 'Too Short!')
.max(100, "Too Long!") .max(100, 'Too Long!')
.required("Required"), .required('Required'),
}); })
const WhatsAppModal = ({ open, onClose, whatsAppId }) => { const WhatsAppModal = ({ open, onClose, whatsAppId }) => {
const classes = useStyles(); const classes = useStyles()
const initialState = { const initialState = {
name: "", name: '',
urlApi: "", urlApi: '',
url: "", url: '',
greetingMessage: "", greetingMessage: '',
farewellMessage: "", farewellMessage: '',
isDefault: false, isDefault: false,
}; }
const [whatsApp, setWhatsApp] = useState(initialState);
const [selectedQueueIds, setSelectedQueueIds] = useState([]); const { user } = useContext(AuthContext)
const [whatsApp, setWhatsApp] = useState(initialState)
const [selectedQueueIds, setSelectedQueueIds] = useState([])
useEffect(() => { useEffect(() => {
const fetchSession = async () => { const fetchSession = async () => {
if (!whatsAppId) return; if (!whatsAppId) return
try { try {
const { data } = await api.get(`whatsapp/${whatsAppId}`); const { data } = await api.get(`whatsapp/${whatsAppId}`)
setWhatsApp(data); setWhatsApp(data)
const whatsQueueIds = data.queues?.map(queue => queue.id); const whatsQueueIds = data.queues?.map((queue) => queue.id)
setSelectedQueueIds(whatsQueueIds); setSelectedQueueIds(whatsQueueIds)
} catch (err) { } catch (err) {
toastError(err); toastError(err)
} }
}; }
fetchSession(); fetchSession()
}, [whatsAppId]); }, [whatsAppId])
const handleSaveWhatsApp = async values => { const handleSaveWhatsApp = async (values) => {
const whatsappData = { ...values, queueIds: selectedQueueIds }; const whatsappData = { ...values, queueIds: selectedQueueIds }
let response = null let response = null
try { try {
if (whatsAppId) { if (whatsAppId) {
response = await api.put(`/whatsapp/${whatsAppId}`, whatsappData); response = await api.put(`/whatsapp/${whatsAppId}`, whatsappData)
} else { } else {
response = await api.post("/whatsapp", whatsappData); response = await api.post('/whatsapp', whatsappData)
} }
console.log('response: ', response.data.message) console.log('response: ', response.data.message)
if(response && response.data.message === 'wrong_number_start'){ if (response && response.data.message === 'wrong_number_start') {
alert('O numero contido no nome da conexão deve iniciar com o código do país!') alert(
'O numero contido no nome da conexão deve iniciar com o código do país!'
)
} else if (response && response.data.message === 'invalid_phone_number') {
alert(
'A quantidade de numeros digitados no nome do contato é invalida! Certifique-se de que você digitou o numero correto acompanhado pelo código do país!'
)
} else if (response && response.data.message === 'no_phone_number') {
alert(
'Para criar/editar uma sessão de Whatsapp é necessário que o numero do Whatsapp acompanhado pelo código do país esteja presente no nome da sessão!'
)
} else {
toast.success(i18n.t('whatsappModal.success'))
handleClose()
} }
else if(response && response.data.message === 'invalid_phone_number'){
alert('A quantidade de numeros digitados no nome do contato é invalida! Certifique-se de que você digitou o numero correto acompanhado pelo código do país!')
}
else if( response && response.data.message === 'no_phone_number'){
alert('Para criar/editar uma sessão de Whatsapp é necessário que o numero do Whatsapp acompanhado pelo código do país esteja presente no nome da sessão!')
}
else{
toast.success(i18n.t("whatsappModal.success"));
handleClose();
}
} catch (err) { } catch (err) {
toastError(err); toastError(err)
}
} }
};
const handleClose = () => { const handleClose = () => {
onClose(); onClose()
setWhatsApp(initialState); setWhatsApp(initialState)
}; }
return ( return (
<div className={classes.root}> <div className={classes.root}>
@ -136,8 +144,8 @@ const WhatsAppModal = ({ open, onClose, whatsAppId }) => {
> >
<DialogTitle> <DialogTitle>
{whatsAppId {whatsAppId
? i18n.t("whatsappModal.title.edit") ? i18n.t('whatsappModal.title.edit')
: i18n.t("whatsappModal.title.add")} : i18n.t('whatsappModal.title.add')}
</DialogTitle> </DialogTitle>
<Formik <Formik
initialValues={whatsApp} initialValues={whatsApp}
@ -145,19 +153,23 @@ const WhatsAppModal = ({ open, onClose, whatsAppId }) => {
validationSchema={SessionSchema} validationSchema={SessionSchema}
onSubmit={(values, actions) => { onSubmit={(values, actions) => {
setTimeout(() => { setTimeout(() => {
handleSaveWhatsApp(values); handleSaveWhatsApp(values)
actions.setSubmitting(false); actions.setSubmitting(false)
}, 400); }, 400)
}} }}
> >
{({ values, touched, errors, isSubmitting }) => ( {({ values, touched, errors, isSubmitting }) => (
<Form> <Form>
<DialogContent dividers> <DialogContent dividers>
<Can
role={user.profile}
perform="url-remote-session:show"
yes={() => (
<>
<div className={classes.multFieldLine}> <div className={classes.multFieldLine}>
<Field <Field
as={TextField} as={TextField}
label={i18n.t("whatsappModal.form.name")} label={i18n.t('whatsappModal.form.name')}
autoFocus autoFocus
name="name" name="name"
error={touched.name && Boolean(errors.name)} error={touched.name && Boolean(errors.name)}
@ -175,16 +187,13 @@ const WhatsAppModal = ({ open, onClose, whatsAppId }) => {
checked={values.isDefault} checked={values.isDefault}
/> />
} }
label={i18n.t("whatsappModal.form.default")} label={i18n.t('whatsappModal.form.default')}
/> />
</div> </div>
<div className={classes.multFieldLine}> <div className={classes.multFieldLine}>
<Field <Field
as={TextField} as={TextField}
label='url API' label="url API"
autoFocus autoFocus
name="urlApi" name="urlApi"
error={touched.name && Boolean(errors.name)} error={touched.name && Boolean(errors.name)}
@ -195,7 +204,7 @@ const WhatsAppModal = ({ open, onClose, whatsAppId }) => {
/> />
<Field <Field
as={TextField} as={TextField}
label='url session' label="url session"
autoFocus autoFocus
name="url" name="url"
error={touched.name && Boolean(errors.name)} error={touched.name && Boolean(errors.name)}
@ -205,12 +214,14 @@ const WhatsAppModal = ({ open, onClose, whatsAppId }) => {
className={classes.textField} className={classes.textField}
/> />
</div> </div>
</>
)}
/>
<div> <div>
<Field <Field
as={TextField} as={TextField}
label={i18n.t("queueModal.form.greetingMessage")} label={i18n.t('queueModal.form.greetingMessage')}
type="greetingMessage" type="greetingMessage"
multiline multiline
rows={5} rows={5}
@ -227,11 +238,10 @@ const WhatsAppModal = ({ open, onClose, whatsAppId }) => {
/> />
</div> </div>
<div> <div>
<Field <Field
as={TextField} as={TextField}
label={i18n.t("whatsappModal.form.farewellMessage")} label={i18n.t('whatsappModal.form.farewellMessage')}
type="farewellMessage" type="farewellMessage"
multiline multiline
rows={5} rows={5}
@ -249,7 +259,7 @@ const WhatsAppModal = ({ open, onClose, whatsAppId }) => {
</div> </div>
<QueueSelect <QueueSelect
selectedQueueIds={selectedQueueIds} selectedQueueIds={selectedQueueIds}
onChange={selectedIds => setSelectedQueueIds(selectedIds)} onChange={(selectedIds) => setSelectedQueueIds(selectedIds)}
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
@ -259,7 +269,7 @@ const WhatsAppModal = ({ open, onClose, whatsAppId }) => {
disabled={isSubmitting} disabled={isSubmitting}
variant="outlined" variant="outlined"
> >
{i18n.t("whatsappModal.buttons.cancel")} {i18n.t('whatsappModal.buttons.cancel')}
</Button> </Button>
<Button <Button
type="submit" type="submit"
@ -269,8 +279,8 @@ const WhatsAppModal = ({ open, onClose, whatsAppId }) => {
className={classes.btnWrapper} className={classes.btnWrapper}
> >
{whatsAppId {whatsAppId
? i18n.t("whatsappModal.buttons.okEdit") ? i18n.t('whatsappModal.buttons.okEdit')
: i18n.t("whatsappModal.buttons.okAdd")} : i18n.t('whatsappModal.buttons.okAdd')}
{isSubmitting && ( {isSubmitting && (
<CircularProgress <CircularProgress
size={24} size={24}
@ -284,7 +294,7 @@ const WhatsAppModal = ({ open, onClose, whatsAppId }) => {
</Formik> </Formik>
</Dialog> </Dialog>
</div> </div>
); )
}; }
export default React.memo(WhatsAppModal); export default React.memo(WhatsAppModal)

View File

@ -1,21 +1,22 @@
import React, { createContext } from "react"; import React, { createContext } from 'react'
import useAuth from "../../hooks/useAuth.js"; import useAuth from '../../hooks/useAuth.js'
const AuthContext = createContext(); const AuthContext = createContext()
const AuthProvider = ({ children }) => { const AuthProvider = ({ children }) => {
const { loading, user, isAuth, handleLogin, handleLogout } = useAuth(); const { loading, user, isAuth, handleLogin, handleLogout, setSetting } =
useAuth()
//{ //{
return ( return (
<AuthContext.Provider <AuthContext.Provider
value={{ loading, user, isAuth, handleLogin, handleLogout }} value={{ loading, user, isAuth, handleLogin, handleLogout, setSetting }}
> >
{children} {children}
</AuthContext.Provider> </AuthContext.Provider>
); )
}; }
export { AuthContext, AuthProvider }; export { AuthContext, AuthProvider }

View File

@ -1,125 +1,158 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from 'react'
import { useHistory } from "react-router-dom"; import { useHistory } from 'react-router-dom'
import openSocket from "socket.io-client"; import openSocket from 'socket.io-client'
import { toast } from "react-toastify"; import { toast } from 'react-toastify'
import { i18n } from "../../translate/i18n"; import { i18n } from '../../translate/i18n'
import api from "../../services/api"; import api from '../../services/api'
import toastError from "../../errors/toastError"; import toastError from '../../errors/toastError'
const useAuth = () => { const useAuth = () => {
const history = useHistory(); const history = useHistory()
const [isAuth, setIsAuth] = useState(false); const [isAuth, setIsAuth] = useState(false)
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true)
const [user, setUser] = useState({}); const [user, setUser] = useState({})
const [setting, setSetting] = useState({})
api.interceptors.request.use( api.interceptors.request.use(
config => { (config) => {
const token = localStorage.getItem("token"); const token = localStorage.getItem('token')
if (token) { if (token) {
config.headers["Authorization"] = `Bearer ${JSON.parse(token)}`; config.headers['Authorization'] = `Bearer ${JSON.parse(token)}`
setIsAuth(true); setIsAuth(true)
} }
return config; return config
}, },
error => { (error) => {
Promise.reject(error); Promise.reject(error)
} }
); )
api.interceptors.response.use( api.interceptors.response.use(
response => { (response) => {
return response; return response
}, },
async error => { async (error) => {
const originalRequest = error.config; const originalRequest = error.config
if (error?.response?.status === 403 && !originalRequest._retry) { if (error?.response?.status === 403 && !originalRequest._retry) {
originalRequest._retry = true; originalRequest._retry = true
const { data } = await api.post("/auth/refresh_token"); const { data } = await api.post('/auth/refresh_token')
if (data) { if (data) {
localStorage.setItem("token", JSON.stringify(data.token)); localStorage.setItem('token', JSON.stringify(data.token))
api.defaults.headers.Authorization = `Bearer ${data.token}`; api.defaults.headers.Authorization = `Bearer ${data.token}`
} }
return api(originalRequest); return api(originalRequest)
} }
if (error?.response?.status === 401) { if (error?.response?.status === 401) {
localStorage.removeItem("token"); localStorage.removeItem('token')
api.defaults.headers.Authorization = undefined; api.defaults.headers.Authorization = undefined
setIsAuth(false); setIsAuth(false)
} }
return Promise.reject(error); return Promise.reject(error)
} }
); )
useEffect(() => { useEffect(() => {
const token = localStorage.getItem("token"); const token = localStorage.getItem('token')
(async () => { ;(async () => {
if (token) { if (token) {
try { try {
const { data } = await api.post("/auth/refresh_token"); const { data } = await api.post('/auth/refresh_token')
api.defaults.headers.Authorization = `Bearer ${data.token}`; api.defaults.headers.Authorization = `Bearer ${data.token}`
setIsAuth(true); setIsAuth(true)
setUser(data.user); setUser(data.user)
} catch (err) { } catch (err) {
toastError(err); toastError(err)
} }
} }
setLoading(false); setLoading(false)
})(); })()
}, []); }, [])
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const fetchSession = async () => {
try {
socket.on("user", data => { const { data } = await api.get('/settings')
if (data.action === "update" && data.user.id === user.id) { setSetting(data)
setUser(data.user); } catch (err) {
toastError(err)
} }
}); }
fetchSession()
}, [])
useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL)
socket.on('user', (data) => {
if (data.action === 'update' && data.user.id === user.id) {
setUser(data.user)
}
})
socket.on('settings', (data) => {
if (data.action === 'update') {
setSetting((prevState) => {
const aux = [...prevState]
const settingIndex = aux.findIndex((s) => s.key === data.setting.key)
aux[settingIndex].value = data.setting.value
return aux
})
}
})
return () => { return () => {
socket.disconnect(); socket.disconnect()
}; }
}, [user]); }, [user])
const handleLogin = async userData => { const handleLogin = async (userData) => {
setLoading(true); setLoading(true)
try { try {
const { data } = await api.post("/auth/login", userData); const { data } = await api.post('/auth/login', userData)
localStorage.setItem("token", JSON.stringify(data.token)); localStorage.setItem('token', JSON.stringify(data.token))
api.defaults.headers.Authorization = `Bearer ${data.token}`; api.defaults.headers.Authorization = `Bearer ${data.token}`
setUser(data.user); setUser(data.user)
setIsAuth(true); setIsAuth(true)
toast.success(i18n.t("auth.toasts.success")); toast.success(i18n.t('auth.toasts.success'))
history.push("/tickets"); history.push('/tickets')
setLoading(false); setLoading(false)
} catch (err) { } catch (err) {
toastError(err); toastError(err)
setLoading(false); setLoading(false)
}
} }
};
const handleLogout = async () => { const handleLogout = async () => {
setLoading(true); setLoading(true)
try { try {
await api.delete("/auth/logout"); await api.delete('/auth/logout')
setIsAuth(false); setIsAuth(false)
setUser({}); setUser({})
localStorage.removeItem("token"); localStorage.removeItem('token')
api.defaults.headers.Authorization = undefined; api.defaults.headers.Authorization = undefined
setLoading(false); setLoading(false)
history.push("/login"); history.push('/login')
} catch (err) { } catch (err) {
toastError(err); toastError(err)
setLoading(false); setLoading(false)
}
} }
};
return { isAuth, user, loading, handleLogin, handleLogout }; return {
}; isAuth,
user,
loading,
handleLogin,
handleLogout,
setting,
setSetting,
}
}
export default useAuth; export default useAuth

View File

@ -1,11 +1,11 @@
import React, { useState, useCallback, useEffect, useContext } from "react"; import React, { useState, useCallback, useEffect, useContext } from 'react'
import { toast } from "react-toastify"; import { toast } from 'react-toastify'
import { format, parseISO } from "date-fns"; import { format, parseISO } from 'date-fns'
import openSocket from "socket.io-client"; import openSocket from 'socket.io-client'
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'
import { import {
Button, Button,
TableBody, TableBody,
@ -18,7 +18,7 @@ import {
Tooltip, Tooltip,
Typography, Typography,
CircularProgress, CircularProgress,
} from "@material-ui/core"; } from '@material-ui/core'
import { import {
Edit, Edit,
CheckCircle, CheckCircle,
@ -28,56 +28,55 @@ import {
CropFree, CropFree,
DeleteOutline, DeleteOutline,
// Restore // Restore
} from "@material-ui/icons"; } from '@material-ui/icons'
import MainContainer from "../../components/MainContainer"; import MainContainer from '../../components/MainContainer'
import MainHeader from "../../components/MainHeader"; import MainHeader from '../../components/MainHeader'
import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper"; import MainHeaderButtonsWrapper from '../../components/MainHeaderButtonsWrapper'
import Title from "../../components/Title"; import Title from '../../components/Title'
import TableRowSkeleton from "../../components/TableRowSkeleton"; import TableRowSkeleton from '../../components/TableRowSkeleton'
import api from "../../services/api"; import api from '../../services/api'
import WhatsAppModal from "../../components/WhatsAppModal"; import WhatsAppModal from '../../components/WhatsAppModal'
import ConfirmationModal from "../../components/ConfirmationModal"; import ConfirmationModal from '../../components/ConfirmationModal'
import QrcodeModal from "../../components/QrcodeModal"; import QrcodeModal from '../../components/QrcodeModal'
import { i18n } from "../../translate/i18n"; import { i18n } from '../../translate/i18n'
import { WhatsAppsContext } from "../../context/WhatsApp/WhatsAppsContext"; import { WhatsAppsContext } from '../../context/WhatsApp/WhatsAppsContext'
import toastError from "../../errors/toastError"; import toastError from '../../errors/toastError'
//-------- //--------
import { AuthContext } from "../../context/Auth/AuthContext"; import { AuthContext } from '../../context/Auth/AuthContext'
import { Can } from "../../components/Can"; import { Can } from '../../components/Can'
const useStyles = makeStyles((theme) => ({
const useStyles = makeStyles(theme => ({
mainPaper: { mainPaper: {
flex: 1, flex: 1,
padding: theme.spacing(1), padding: theme.spacing(1),
overflowY: "scroll", overflowY: 'scroll',
...theme.scrollbarStyles, ...theme.scrollbarStyles,
}, },
customTableCell: { customTableCell: {
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
}, },
tooltip: { tooltip: {
backgroundColor: "#f5f5f9", backgroundColor: '#f5f5f9',
color: "rgba(0, 0, 0, 0.87)", color: 'rgba(0, 0, 0, 0.87)',
fontSize: theme.typography.pxToRem(14), fontSize: theme.typography.pxToRem(14),
border: "1px solid #dadde9", border: '1px solid #dadde9',
maxWidth: 450, maxWidth: 450,
}, },
tooltipPopper: { tooltipPopper: {
textAlign: "center", textAlign: 'center',
}, },
buttonProgress: { buttonProgress: {
color: green[500], color: green[500],
}, },
})); }))
const CustomToolTip = ({ title, content, children }) => { const CustomToolTip = ({ title, content, children }) => {
const classes = useStyles(); const classes = useStyles()
return ( return (
<Tooltip <Tooltip
@ -97,189 +96,195 @@ const CustomToolTip = ({ title, content, children }) => {
> >
{children} {children}
</Tooltip> </Tooltip>
); )
}; }
const Connections = () => { const Connections = () => {
//-------- //--------
const { user } = useContext(AuthContext); const { user } = useContext(AuthContext)
const classes = useStyles()
const classes = useStyles(); const { whatsApps, loading } = useContext(WhatsAppsContext)
const [whatsAppModalOpen, setWhatsAppModalOpen] = useState(false)
const [qrModalOpen, setQrModalOpen] = useState(false)
const [selectedWhatsApp, setSelectedWhatsApp] = useState(null)
const [confirmModalOpen, setConfirmModalOpen] = useState(false)
const { whatsApps, loading } = useContext(WhatsAppsContext); const [diskSpaceInfo, setDiskSpaceInfo] = useState({})
const [whatsAppModalOpen, setWhatsAppModalOpen] = useState(false);
const [qrModalOpen, setQrModalOpen] = useState(false);
const [selectedWhatsApp, setSelectedWhatsApp] = useState(null);
const [confirmModalOpen, setConfirmModalOpen] = useState(false);
const [diskSpaceInfo, setDiskSpaceInfo] = useState({}); const [disabled, setDisabled] = useState(true)
const [disabled, setDisabled] = useState(true); const [settings, setSettings] = useState([])
const [buttons, setClicks] = useState([]) const [buttons, setClicks] = useState([])
const confirmationModalInitialState = { const confirmationModalInitialState = {
action: "", action: '',
title: "", title: '',
message: "", message: '',
whatsAppId: "", whatsAppId: '',
open: false, open: false,
}; }
const [confirmModalInfo, setConfirmModalInfo] = useState( const [confirmModalInfo, setConfirmModalInfo] = useState(
confirmationModalInitialState confirmationModalInitialState
); )
const handleStartWhatsAppSession = async whatsAppId => { useEffect(() => {
const fetchSession = async () => {
try { try {
await api.post(`/whatsappsession/${whatsAppId}`); const { data } = await api.get('/settings')
setSettings(data)
} catch (err) { } catch (err) {
toastError(err); toastError(err)
} }
}; }
fetchSession()
}, [])
const getSettingValue = (key) => {
const { value } = settings.find((s) => s.key === key)
return value
}
const handleStartWhatsAppSession = async (whatsAppId) => {
const handleRestartWhatsAppSession = async whatsapp => {
try { try {
await api.post(`/whatsappsession/${whatsAppId}`)
} catch (err) {
toastError(err)
}
}
const handleRestartWhatsAppSession = async (whatsapp) => {
try {
whatsapp.disabled = true whatsapp.disabled = true
setClicks([...buttons, whatsapp.id]) setClicks([...buttons, whatsapp.id])
function enable_button(whatsappId) { function enable_button(whatsappId) {
setClicks((buttons) =>
setClicks(buttons => buttons.filter(id => { return +id !== +whatsappId }),); buttons.filter((id) => {
return +id !== +whatsappId
})
)
} }
setTimeout(enable_button, 25000, whatsapp.id) setTimeout(enable_button, 25000, whatsapp.id)
await api.post(`/restartwhatsappsession/${whatsapp.id}`); await api.post(`/restartwhatsappsession/${whatsapp.id}`)
} catch (err) { } catch (err) {
toastError(err); toastError(err)
}
} }
};
useEffect(() => { useEffect(() => {
// whatsApps.map((e) => { // whatsApps.map((e) => {
// if (buttons.includes(e.id)) { // if (buttons.includes(e.id)) {
// e.disabled = true // e.disabled = true
// } // }
// }) // })
for (let i = 0; i < whatsApps.length; i++) { for (let i = 0; i < whatsApps.length; i++) {
if (buttons.includes(whatsApps[i].id)) { if (buttons.includes(whatsApps[i].id)) {
whatsApps[i].disabled = true whatsApps[i].disabled = true
} }
} }
}, [whatsApps, buttons]) }, [whatsApps, buttons])
const handleRequestNewQrCode = async (whatsAppId) => {
const handleRequestNewQrCode = async whatsAppId => {
try { try {
await api.put(`/whatsappsession/${whatsAppId}`); await api.put(`/whatsappsession/${whatsAppId}`)
} catch (err) { } catch (err) {
toastError(err); toastError(err)
}
} }
};
const handleOpenWhatsAppModal = () => { const handleOpenWhatsAppModal = () => {
setSelectedWhatsApp(null); setSelectedWhatsApp(null)
setWhatsAppModalOpen(true); setWhatsAppModalOpen(true)
}; }
const handleCloseWhatsAppModal = useCallback(() => { const handleCloseWhatsAppModal = useCallback(() => {
setWhatsAppModalOpen(false); setWhatsAppModalOpen(false)
setSelectedWhatsApp(null); setSelectedWhatsApp(null)
}, [setSelectedWhatsApp, setWhatsAppModalOpen]); }, [setSelectedWhatsApp, setWhatsAppModalOpen])
const handleOpenQrModal = whatsApp => { const handleOpenQrModal = (whatsApp) => {
setSelectedWhatsApp(whatsApp); setSelectedWhatsApp(whatsApp)
setQrModalOpen(true); setQrModalOpen(true)
}; }
const handleCloseQrModal = useCallback(() => { const handleCloseQrModal = useCallback(() => {
setSelectedWhatsApp(null); setSelectedWhatsApp(null)
setQrModalOpen(false); setQrModalOpen(false)
}, [setQrModalOpen, setSelectedWhatsApp]); }, [setQrModalOpen, setSelectedWhatsApp])
const handleEditWhatsApp = whatsApp => { const handleEditWhatsApp = (whatsApp) => {
setSelectedWhatsApp(whatsApp); setSelectedWhatsApp(whatsApp)
setWhatsAppModalOpen(true); setWhatsAppModalOpen(true)
}; }
const handleOpenConfirmationModal = (action, whatsAppId) => { const handleOpenConfirmationModal = (action, whatsAppId) => {
if (action === "disconnect") { if (action === 'disconnect') {
setConfirmModalInfo({ setConfirmModalInfo({
action: action, action: action,
title: i18n.t("connections.confirmationModal.disconnectTitle"), title: i18n.t('connections.confirmationModal.disconnectTitle'),
message: i18n.t("connections.confirmationModal.disconnectMessage"), message: i18n.t('connections.confirmationModal.disconnectMessage'),
whatsAppId: whatsAppId, whatsAppId: whatsAppId,
}); })
} }
if (action === "delete") { if (action === 'delete') {
setConfirmModalInfo({ setConfirmModalInfo({
action: action, action: action,
title: i18n.t("connections.confirmationModal.deleteTitle"), title: i18n.t('connections.confirmationModal.deleteTitle'),
message: i18n.t("connections.confirmationModal.deleteMessage"), message: i18n.t('connections.confirmationModal.deleteMessage'),
whatsAppId: whatsAppId, whatsAppId: whatsAppId,
}); })
}
setConfirmModalOpen(true)
} }
setConfirmModalOpen(true);
};
const handleSubmitConfirmationModal = async () => { const handleSubmitConfirmationModal = async () => {
if (confirmModalInfo.action === "disconnect") { if (confirmModalInfo.action === 'disconnect') {
try { try {
await api.delete(`/whatsappsession/${confirmModalInfo.whatsAppId}`); await api.delete(`/whatsappsession/${confirmModalInfo.whatsAppId}`)
} catch (err) { } catch (err) {
toastError(err); toastError(err)
} }
} }
if (confirmModalInfo.action === "delete") { if (confirmModalInfo.action === 'delete') {
try { try {
await api.delete(`/whatsapp/${confirmModalInfo.whatsAppId}`); await api.delete(`/whatsapp/${confirmModalInfo.whatsAppId}`)
toast.success(i18n.t("connections.toasts.deleted")); toast.success(i18n.t('connections.toasts.deleted'))
} catch (err) { } catch (err) {
toastError(err); toastError(err)
} }
} }
setConfirmModalInfo(confirmationModalInitialState); setConfirmModalInfo(confirmationModalInitialState)
}; }
const renderActionButtons = whatsApp => { const renderActionButtons = (whatsApp) => {
return ( return (
<Can <Can
role={user.profile} role={user.profile}
perform="connection-button:show" perform="connection-button:show"
yes={() => ( yes={() => (
<> <>
{whatsApp.status === "qrcode" && ( {whatsApp.status === 'qrcode' && (
<Button <Button
size="small" size="small"
variant="contained" variant="contained"
color="primary" color="primary"
onClick={() => handleOpenQrModal(whatsApp)} onClick={() => handleOpenQrModal(whatsApp)}
> >
{i18n.t("connections.buttons.qrcode")} {i18n.t('connections.buttons.qrcode')}
</Button> </Button>
)} )}
{whatsApp.status === "DISCONNECTED" && ( {whatsApp.status === 'DISCONNECTED' && (
<> <>
<Button <Button
size="small" size="small"
@ -287,76 +292,74 @@ const Connections = () => {
color="primary" color="primary"
onClick={() => handleStartWhatsAppSession(whatsApp.id)} onClick={() => handleStartWhatsAppSession(whatsApp.id)}
> >
{i18n.t("connections.buttons.tryAgain")} {i18n.t('connections.buttons.tryAgain')}
</Button>{" "} </Button>{' '}
<Button <Button
size="small" size="small"
variant="outlined" variant="outlined"
color="secondary" color="secondary"
onClick={() => handleRequestNewQrCode(whatsApp.id)} onClick={() => handleRequestNewQrCode(whatsApp.id)}
> >
{i18n.t("connections.buttons.newQr")} {i18n.t('connections.buttons.newQr')}
</Button> </Button>
</> </>
)} )}
{(whatsApp.status === "CONNECTED" || {(whatsApp.status === 'CONNECTED' ||
whatsApp.status === "PAIRING" || whatsApp.status === 'PAIRING' ||
whatsApp.status === "TIMEOUT") && ( whatsApp.status === 'TIMEOUT') && (
<Button <Button
size="small" size="small"
variant="outlined" variant="outlined"
color="secondary" color="secondary"
onClick={() => { onClick={() => {
handleOpenConfirmationModal("disconnect", whatsApp.id); handleOpenConfirmationModal('disconnect', whatsApp.id)
}} }}
> >
{i18n.t("connections.buttons.disconnect")} {i18n.t('connections.buttons.disconnect')}
</Button> </Button>
)} )}
{whatsApp.status === "OPENING" && ( {whatsApp.status === 'OPENING' && (
<Button size="small" variant="outlined" disabled color="default"> <Button size="small" variant="outlined" disabled color="default">
{i18n.t("connections.buttons.connecting")} {i18n.t('connections.buttons.connecting')}
</Button> </Button>
)} )}
</> </>
)} )}
/> />
)
}
); const renderStatusToolTips = (whatsApp) => {
};
const renderStatusToolTips = whatsApp => {
return ( return (
<div className={classes.customTableCell}> <div className={classes.customTableCell}>
{whatsApp.status === "DISCONNECTED" && ( {whatsApp.status === 'DISCONNECTED' && (
<CustomToolTip <CustomToolTip
title={i18n.t("connections.toolTips.disconnected.title")} title={i18n.t('connections.toolTips.disconnected.title')}
content={i18n.t("connections.toolTips.disconnected.content")} content={i18n.t('connections.toolTips.disconnected.content')}
> >
<SignalCellularConnectedNoInternet0Bar color="secondary" /> <SignalCellularConnectedNoInternet0Bar color="secondary" />
</CustomToolTip> </CustomToolTip>
)} )}
{whatsApp.status === "OPENING" && ( {whatsApp.status === 'OPENING' && (
<CircularProgress size={24} className={classes.buttonProgress} /> <CircularProgress size={24} className={classes.buttonProgress} />
)} )}
{whatsApp.status === "qrcode" && ( {whatsApp.status === 'qrcode' && (
<CustomToolTip <CustomToolTip
title={i18n.t("connections.toolTips.qrcode.title")} title={i18n.t('connections.toolTips.qrcode.title')}
content={i18n.t("connections.toolTips.qrcode.content")} content={i18n.t('connections.toolTips.qrcode.content')}
> >
<CropFree /> <CropFree />
</CustomToolTip> </CustomToolTip>
)} )}
{whatsApp.status === "CONNECTED" && ( {whatsApp.status === 'CONNECTED' && (
<CustomToolTip title={i18n.t("connections.toolTips.connected.title")}> <CustomToolTip title={i18n.t('connections.toolTips.connected.title')}>
<SignalCellular4Bar style={{ color: green[500] }} /> <SignalCellular4Bar style={{ color: green[500] }} />
</CustomToolTip> </CustomToolTip>
)} )}
{(whatsApp.status === "TIMEOUT" || whatsApp.status === "PAIRING") && ( {(whatsApp.status === 'TIMEOUT' || whatsApp.status === 'PAIRING') && (
<CustomToolTip <CustomToolTip
title={i18n.t("connections.toolTips.timeout.title")} title={i18n.t('connections.toolTips.timeout.title')}
content={i18n.t("connections.toolTips.timeout.content")} content={i18n.t('connections.toolTips.timeout.content')}
> >
<SignalCellularConnectedNoInternet2Bar color="secondary" /> <SignalCellularConnectedNoInternet2Bar color="secondary" />
</CustomToolTip> </CustomToolTip>
@ -371,62 +374,65 @@ const Connections = () => {
</CustomToolTip> </CustomToolTip>
)} */} )} */}
</div> </div>
); )
}; }
useEffect(() => { useEffect(() => {
const delayDebounceFn = setTimeout(() => { const delayDebounceFn = setTimeout(() => {
const fetchQueries = async () => { const fetchQueries = async () => {
try { try {
await api.post(`/restartwhatsappsession/0`, {
await api.post(`/restartwhatsappsession/0`, { params: { status: 'status' }, }); params: { status: 'status' },
})
setDisabled(false) setDisabled(false)
setClicks(buttons => buttons.map((e) => { return { id: e.id, disabled: false } })) setClicks((buttons) =>
buttons.map((e) => {
return { id: e.id, disabled: false }
})
)
} catch (err) { } catch (err) {
console.log(err); console.log(err)
}
} }
};
fetchQueries();
}, 500);
return () => clearTimeout(delayDebounceFn);
}, []);
fetchQueries()
}, 500)
return () => clearTimeout(delayDebounceFn)
}, [])
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket(process.env.REACT_APP_BACKEND_URL)
socket.on("diskSpaceMonit", data => {
if (data.action === "update") {
socket.on('diskSpaceMonit', (data) => {
if (data.action === 'update') {
setDiskSpaceInfo(data.diskSpace) setDiskSpaceInfo(data.diskSpace)
} }
}); })
socket.on('settings', (data) => {
if (data.action === 'update') {
setSettings((prevState) => {
const aux = [...prevState]
const settingIndex = aux.findIndex((s) => s.key === data.setting.key)
aux[settingIndex].value = data.setting.value
return aux
})
}
})
return () => { return () => {
socket.disconnect(); socket.disconnect()
}; }
}, []); }, [])
return ( return (
<Can <Can
role={user.profile} role={user.profile}
perform="connections-view:show" perform="connections-view:show"
yes={() => ( yes={() => (
<MainContainer> <MainContainer>
<ConfirmationModal <ConfirmationModal
title={confirmModalInfo.title} title={confirmModalInfo.title}
open={confirmModalOpen} open={confirmModalOpen}
@ -436,7 +442,6 @@ const Connections = () => {
{confirmModalInfo.message} {confirmModalInfo.message}
</ConfirmationModal> </ConfirmationModal>
<QrcodeModal <QrcodeModal
open={qrModalOpen} open={qrModalOpen}
onClose={handleCloseQrModal} onClose={handleCloseQrModal}
@ -450,32 +455,28 @@ const Connections = () => {
/> />
<MainHeader> <MainHeader>
<Title>{i18n.t('connections.title')}</Title>
<Title>{i18n.t("connections.title")}</Title>
<MainHeaderButtonsWrapper> <MainHeaderButtonsWrapper>
<Can <Can
role={user.profile} role={user.profile}
perform="btn-add-whatsapp" updatedAt perform="btn-add-whatsapp"
updatedAt
yes={() => ( yes={() => (
<Button <Button
variant="contained" variant="contained"
color="primary" color="primary"
onClick={handleOpenWhatsAppModal}> onClick={handleOpenWhatsAppModal}
{i18n.t("connections.buttons.add")} >
{i18n.t('connections.buttons.add')}
</Button> </Button>
)} )}
/> />
</MainHeaderButtonsWrapper> </MainHeaderButtonsWrapper>
</MainHeader> </MainHeader>
<Paper className={classes.mainPaper} variant="outlined"> <Paper className={classes.mainPaper} variant="outlined">
<> <>
<Can <Can
role={user.profile} role={user.profile}
perform="space-disk-info:show" perform="space-disk-info:show"
@ -484,27 +485,27 @@ const Connections = () => {
<Table size="small"> <Table size="small">
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell align="center"> <TableCell align="center">Size</TableCell>
Size <TableCell align="center">Used</TableCell>
</TableCell> <TableCell align="center">Available</TableCell>
<TableCell align="center"> <TableCell align="center">Use%</TableCell>
Used
</TableCell>
<TableCell align="center">
Available
</TableCell>
<TableCell align="center">
Use%
</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
<TableRow> <TableRow>
<TableCell align="center">{diskSpaceInfo.size}</TableCell> <TableCell align="center">
<TableCell align="center">{diskSpaceInfo.used}</TableCell> {diskSpaceInfo.size}
<TableCell align="center">{diskSpaceInfo.available}</TableCell> </TableCell>
<TableCell align="center">{diskSpaceInfo.use}</TableCell> <TableCell align="center">
{diskSpaceInfo.used}
</TableCell>
<TableCell align="center">
{diskSpaceInfo.available}
</TableCell>
<TableCell align="center">
{diskSpaceInfo.use}
</TableCell>
</TableRow> </TableRow>
</TableBody> </TableBody>
</Table> </Table>
@ -516,13 +517,12 @@ const Connections = () => {
<Table size="small"> <Table size="small">
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell align="center"> <TableCell align="center">
{i18n.t("connections.table.name")} {i18n.t('connections.table.name')}
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
{i18n.t("connections.table.status")} {i18n.t('connections.table.status')}
</TableCell> </TableCell>
<Can <Can
@ -530,47 +530,33 @@ const Connections = () => {
perform="connection-button:show" perform="connection-button:show"
yes={() => ( yes={() => (
<TableCell align="center"> <TableCell align="center">
{i18n.t("connections.table.session")} {i18n.t('connections.table.session')}
</TableCell> </TableCell>
)} )}
/> />
<Can
role={user.profile}
perform="connection-button:show"
yes={() => <TableCell align="center">Restore</TableCell>}
/>
<Can <Can
role={user.profile} role={user.profile}
perform="connection-button:show" perform="connection-button:show"
yes={() => ( yes={() => (
<TableCell align="center"> <TableCell align="center">Session MB</TableCell>
Restore
</TableCell>
)} )}
/> />
<Can
role={user.profile}
perform="connection-button:show"
yes={() => (
<TableCell align="center"> <TableCell align="center">
Session MB {i18n.t('connections.table.lastUpdate')}
</TableCell>
)}
/>
<TableCell align="center">
{i18n.t("connections.table.lastUpdate")}
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
{i18n.t("connections.table.default")} {i18n.t('connections.table.default')}
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
{i18n.t("connections.table.actions")} {i18n.t('connections.table.actions')}
</TableCell> </TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
@ -580,9 +566,11 @@ const Connections = () => {
) : ( ) : (
<> <>
{whatsApps?.length > 0 && {whatsApps?.length > 0 &&
whatsApps.map(whatsApp => ( whatsApps.map((whatsApp) => (
<TableRow key={whatsApp.id}> <TableRow key={whatsApp.id}>
<TableCell align="center">{whatsApp.name}</TableCell> <TableCell align="center">
{whatsApp.name}
</TableCell>
<TableCell align="center"> <TableCell align="center">
{renderStatusToolTips(whatsApp)} {renderStatusToolTips(whatsApp)}
@ -598,36 +586,30 @@ const Connections = () => {
)} )}
/> />
<Can <Can
role={user.profile} role={user.profile}
perform="connection-button:show" perform="connection-button:show"
yes={() => ( yes={() => (
<TableCell align="center"> <TableCell align="center">
<Button <Button
disabled={whatsApp.disabled || disabled ? true : false} disabled={
whatsApp.disabled || disabled
? true
: false
}
size="small" size="small"
variant="contained" variant="contained"
color="primary" color="primary"
onClick={() => handleRestartWhatsAppSession(whatsApp)} onClick={() =>
handleRestartWhatsAppSession(whatsApp)
}
> >
Restore Restore
</Button> </Button>
</TableCell> </TableCell>
)} )}
/> />
<Can <Can
role={user.profile} role={user.profile}
perform="connection-button:show" perform="connection-button:show"
@ -635,7 +617,9 @@ const Connections = () => {
<TableCell align="center"> <TableCell align="center">
<CustomToolTip <CustomToolTip
title={'Informação da sessão em Megabytes'} title={'Informação da sessão em Megabytes'}
content={'Tamanho do diretorio da sessão atualizado a cada 5 segundos'} content={
'Tamanho do diretorio da sessão atualizado a cada 5 segundos'
}
> >
<div>{whatsApp.sessionSize}</div> <div>{whatsApp.sessionSize}</div>
</CustomToolTip> </CustomToolTip>
@ -643,15 +627,13 @@ const Connections = () => {
)} )}
/> />
<TableCell align="center"> <TableCell align="center">
{format(parseISO(whatsApp.updatedAt), "dd/MM/yy HH:mm")} {format(
parseISO(whatsApp.updatedAt),
'dd/MM/yy HH:mm'
)}
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
{whatsApp.isDefault && ( {whatsApp.isDefault && (
<div className={classes.customTableCell}> <div className={classes.customTableCell}>
@ -660,38 +642,62 @@ const Connections = () => {
)} )}
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
<Can <Can
role={user.profile} role={user.profile}
perform="show-icon-edit-whatsapp" perform="show-icon-edit-whatsapp"
yes={() => ( yes={() => (
// disabled={
// whatsApp.disabled || disabled
// ? true
// : false
// }
<div
style={{
margin: 0,
padding: 0,
border: 'none',
display: 'inline',
}}
>
{(settings &&
settings.length > 0 &&
getSettingValue('editURA') &&
getSettingValue('editURA') ===
'enabled') |
(user.profile === 'master') ? (
<IconButton <IconButton
size="small" size="small"
onClick={() => handleEditWhatsApp(whatsApp)} onClick={() =>
handleEditWhatsApp(whatsApp)
}
> >
<Edit /> <Edit />
</IconButton> </IconButton>
) : (
<div></div>
)}
</div>
)} )}
/> />
<Can <Can
role={user.profile} role={user.profile}
perform="btn-remove-whatsapp" perform="btn-remove-whatsapp"
yes={() => ( yes={() => (
<IconButton <IconButton
size="small" size="small"
onClick={e => { onClick={(e) => {
handleOpenConfirmationModal("delete", whatsApp.id); handleOpenConfirmationModal(
'delete',
whatsApp.id
)
}} }}
> >
<DeleteOutline /> <DeleteOutline />
</IconButton> </IconButton>
)} )}
/> />
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}
@ -699,14 +705,12 @@ const Connections = () => {
)} )}
</TableBody> </TableBody>
</Table> </Table>
</> </>
</Paper> </Paper>
</MainContainer> </MainContainer>
)} )}
/> />
)
}
); export default Connections
};
export default Connections;

View File

@ -1,6 +1,6 @@
import React, { useEffect, useReducer, useState, useContext } from "react"; import React, { useEffect, useReducer, useState, useContext } from 'react'
import openSocket from "socket.io-client"; import openSocket from 'socket.io-client'
import { import {
Button, Button,
@ -13,155 +13,186 @@ import {
TableHead, TableHead,
TableRow, TableRow,
Typography, Typography,
} from "@material-ui/core"; } from '@material-ui/core'
import MainContainer from "../../components/MainContainer"; import MainContainer from '../../components/MainContainer'
import MainHeader from "../../components/MainHeader"; import MainHeader from '../../components/MainHeader'
import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper"; import MainHeaderButtonsWrapper from '../../components/MainHeaderButtonsWrapper'
import TableRowSkeleton from "../../components/TableRowSkeleton"; import TableRowSkeleton from '../../components/TableRowSkeleton'
import Title from "../../components/Title"; import Title from '../../components/Title'
import { i18n } from "../../translate/i18n"; import { i18n } from '../../translate/i18n'
import toastError from "../../errors/toastError"; import toastError from '../../errors/toastError'
import api from "../../services/api"; import api from '../../services/api'
import { DeleteOutline, Edit } from "@material-ui/icons"; import { DeleteOutline, Edit } from '@material-ui/icons'
import QueueModal from "../../components/QueueModal"; import QueueModal from '../../components/QueueModal'
import { toast } from "react-toastify"; import { toast } from 'react-toastify'
import ConfirmationModal from "../../components/ConfirmationModal"; import ConfirmationModal from '../../components/ConfirmationModal'
import { AuthContext } from "../../context/Auth/AuthContext"; import { AuthContext } from '../../context/Auth/AuthContext'
import { Can } from "../../components/Can"; import { Can } from '../../components/Can'
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
mainPaper: { mainPaper: {
flex: 1, flex: 1,
padding: theme.spacing(1), padding: theme.spacing(1),
overflowY: "scroll", overflowY: 'scroll',
...theme.scrollbarStyles, ...theme.scrollbarStyles,
}, },
customTableCell: { customTableCell: {
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
justifyContent: "center", justifyContent: 'center',
}, },
})); }))
const reducer = (state, action) => { const reducer = (state, action) => {
if (action.type === "LOAD_QUEUES") { if (action.type === 'LOAD_QUEUES') {
const queues = action.payload; const queues = action.payload
const newQueues = []; const newQueues = []
queues.forEach((queue) => { queues.forEach((queue) => {
const queueIndex = state.findIndex((q) => q.id === queue.id); const queueIndex = state.findIndex((q) => q.id === queue.id)
if (queueIndex !== -1) { if (queueIndex !== -1) {
state[queueIndex] = queue; state[queueIndex] = queue
} else { } else {
newQueues.push(queue); newQueues.push(queue)
} }
}); })
return [...state, ...newQueues]; return [...state, ...newQueues]
} }
if (action.type === "UPDATE_QUEUES") { if (action.type === 'UPDATE_QUEUES') {
const queue = action.payload; const queue = action.payload
const queueIndex = state.findIndex((u) => u.id === queue.id); const queueIndex = state.findIndex((u) => u.id === queue.id)
if (queueIndex !== -1) { if (queueIndex !== -1) {
state[queueIndex] = queue; state[queueIndex] = queue
return [...state]; return [...state]
} else { } else {
return [queue, ...state]; return [queue, ...state]
} }
} }
if (action.type === "DELETE_QUEUE") { if (action.type === 'DELETE_QUEUE') {
const queueId = action.payload; const queueId = action.payload
const queueIndex = state.findIndex((q) => q.id === queueId); const queueIndex = state.findIndex((q) => q.id === queueId)
if (queueIndex !== -1) { if (queueIndex !== -1) {
state.splice(queueIndex, 1); state.splice(queueIndex, 1)
} }
return [...state]; return [...state]
} }
if (action.type === "RESET") { if (action.type === 'RESET') {
return []; return []
} }
}; }
const Queues = () => { const Queues = () => {
const classes = useStyles(); const classes = useStyles()
const { user } = useContext(AuthContext); const { user } = useContext(AuthContext)
const [queues, dispatch] = useReducer(reducer, []); const [queues, dispatch] = useReducer(reducer, [])
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false)
const [queueModalOpen, setQueueModalOpen] = useState(false); const [queueModalOpen, setQueueModalOpen] = useState(false)
const [selectedQueue, setSelectedQueue] = useState(null); const [selectedQueue, setSelectedQueue] = useState(null)
const [confirmModalOpen, setConfirmModalOpen] = useState(false); const [confirmModalOpen, setConfirmModalOpen] = useState(false)
const [settings, setSettings] = useState([])
useEffect(() => { useEffect(() => {
(async () => { ;(async () => {
setLoading(true); setLoading(true)
try { try {
const { data } = await api.get("/queue"); const { data } = await api.get('/queue')
dispatch({ type: "LOAD_QUEUES", payload: data }); dispatch({ type: 'LOAD_QUEUES', payload: data })
setLoading(false); setLoading(false)
} catch (err) { } catch (err) {
toastError(err); toastError(err)
setLoading(false); setLoading(false)
} }
})(); })()
}, []); }, [])
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const fetchSession = async () => {
try {
const { data } = await api.get('/settings')
setSettings(data)
} catch (err) {
toastError(err)
}
}
fetchSession()
}, [])
socket.on("queue", (data) => { const getSettingValue = (key) => {
if (data.action === "update" || data.action === "create") { const { value } = settings.find((s) => s.key === key)
dispatch({ type: "UPDATE_QUEUES", payload: data.queue });
return value
} }
if (data.action === "delete") { useEffect(() => {
dispatch({ type: "DELETE_QUEUE", payload: data.queueId }); const socket = openSocket(process.env.REACT_APP_BACKEND_URL)
socket.on('queue', (data) => {
if (data.action === 'update' || data.action === 'create') {
dispatch({ type: 'UPDATE_QUEUES', payload: data.queue })
} }
});
if (data.action === 'delete') {
dispatch({ type: 'DELETE_QUEUE', payload: data.queueId })
}
})
socket.on('settings', (data) => {
if (data.action === 'update') {
setSettings((prevState) => {
const aux = [...prevState]
const settingIndex = aux.findIndex((s) => s.key === data.setting.key)
aux[settingIndex].value = data.setting.value
return aux
})
}
})
return () => { return () => {
socket.disconnect(); socket.disconnect()
}; }
}, []); }, [])
const handleOpenQueueModal = () => { const handleOpenQueueModal = () => {
setQueueModalOpen(true); setQueueModalOpen(true)
setSelectedQueue(null); setSelectedQueue(null)
}; }
const handleCloseQueueModal = () => { const handleCloseQueueModal = () => {
setQueueModalOpen(false); setQueueModalOpen(false)
setSelectedQueue(null); setSelectedQueue(null)
}; }
const handleEditQueue = (queue) => { const handleEditQueue = (queue) => {
setSelectedQueue(queue); setSelectedQueue(queue)
setQueueModalOpen(true); setQueueModalOpen(true)
}; }
const handleCloseConfirmationModal = () => { const handleCloseConfirmationModal = () => {
setConfirmModalOpen(false); setConfirmModalOpen(false)
setSelectedQueue(null); setSelectedQueue(null)
}; }
const handleDeleteQueue = async (queueId) => { const handleDeleteQueue = async (queueId) => {
try { try {
await api.delete(`/queue/${queueId}`); await api.delete(`/queue/${queueId}`)
toast.success(i18n.t("Queue deleted successfully!")); toast.success(i18n.t('Queue deleted successfully!'))
} catch (err) { } catch (err) {
toastError(err); toastError(err)
}
setSelectedQueue(null)
} }
setSelectedQueue(null);
};
return ( return (
<Can <Can
@ -172,14 +203,15 @@ const Queues = () => {
<ConfirmationModal <ConfirmationModal
title={ title={
selectedQueue && selectedQueue &&
`${i18n.t("queues.confirmationModal.deleteTitle")} ${selectedQueue.name `${i18n.t('queues.confirmationModal.deleteTitle')} ${
selectedQueue.name
}?` }?`
} }
open={confirmModalOpen} open={confirmModalOpen}
onClose={handleCloseConfirmationModal} onClose={handleCloseConfirmationModal}
onConfirm={() => handleDeleteQueue(selectedQueue.id)} onConfirm={() => handleDeleteQueue(selectedQueue.id)}
> >
{i18n.t("queues.confirmationModal.deleteMessage")} {i18n.t('queues.confirmationModal.deleteMessage')}
</ConfirmationModal> </ConfirmationModal>
<QueueModal <QueueModal
open={queueModalOpen} open={queueModalOpen}
@ -187,8 +219,7 @@ const Queues = () => {
queueId={selectedQueue?.id} queueId={selectedQueue?.id}
/> />
<MainHeader> <MainHeader>
<Title>{i18n.t("queues.title")}</Title> <Title>{i18n.t('queues.title')}</Title>
<Can <Can
role={user.profile} role={user.profile}
@ -200,29 +231,27 @@ const Queues = () => {
color="primary" color="primary"
onClick={handleOpenQueueModal} onClick={handleOpenQueueModal}
> >
{i18n.t("queues.buttons.add")} {i18n.t('queues.buttons.add')}
</Button> </Button>
</MainHeaderButtonsWrapper> </MainHeaderButtonsWrapper>
)} )}
/> />
</MainHeader> </MainHeader>
<Paper className={classes.mainPaper} variant="outlined"> <Paper className={classes.mainPaper} variant="outlined">
<Table size="small"> <Table size="small">
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell align="center"> <TableCell align="center">
{i18n.t("queues.table.name")} {i18n.t('queues.table.name')}
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
{i18n.t("queues.table.color")} {i18n.t('queues.table.color')}
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
{i18n.t("queues.table.greeting")} {i18n.t('queues.table.greeting')}
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
{i18n.t("queues.table.actions")} {i18n.t('queues.table.actions')}
</TableCell> </TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
@ -238,7 +267,7 @@ const Queues = () => {
backgroundColor: queue.color, backgroundColor: queue.color,
width: 60, width: 60,
height: 20, height: 20,
alignSelf: "center", alignSelf: 'center',
}} }}
/> />
</div> </div>
@ -246,7 +275,7 @@ const Queues = () => {
<TableCell align="center"> <TableCell align="center">
<div className={classes.customTableCell}> <div className={classes.customTableCell}>
<Typography <Typography
style={{ width: 300, align: "center" }} style={{ width: 300, align: 'center' }}
noWrap noWrap
variant="body2" variant="body2"
> >
@ -255,25 +284,44 @@ const Queues = () => {
</div> </div>
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
<Can <Can
role={user.profile} role={user.profile}
perform="show-icon-edit-queue" perform="show-icon-edit-queue"
yes={() => ( yes={() => (
<div
style={{
margin: 0,
padding: 0,
border: 'none',
display: 'inline',
}}
>
{(settings &&
settings.length > 0 &&
getSettingValue('editQueue') &&
getSettingValue('editQueue') === 'enabled') |
(user.profile === 'master') ? (
<IconButton <IconButton
size="small" size="small"
onClick={() => handleEditQueue(queue)} onClick={() => handleEditQueue(queue)}
> >
<Edit /> <Edit />
</IconButton> </IconButton>
) : (
<div></div>
)}
</div>
// <IconButton
// size="small"
// onClick={() => handleEditQueue(queue)}
// >
// <Edit />
// </IconButton>
)} )}
/> />
<Can <Can
role={user.profile} role={user.profile}
perform="show-icon-delete-queue" perform="show-icon-delete-queue"
@ -281,15 +329,14 @@ const Queues = () => {
<IconButton <IconButton
size="small" size="small"
onClick={() => { onClick={() => {
setSelectedQueue(queue); setSelectedQueue(queue)
setConfirmModalOpen(true); setConfirmModalOpen(true)
}} }}
> >
<DeleteOutline /> <DeleteOutline />
</IconButton> </IconButton>
)} )}
/> />
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}
@ -301,7 +348,7 @@ const Queues = () => {
</MainContainer> </MainContainer>
)} )}
/> />
); )
}; }
export default Queues; export default Queues

View File

@ -1,128 +1,125 @@
import React, { useState, useEffect, useContext} from "react"; import React, { useState, useEffect, useContext } from 'react'
import openSocket from "socket.io-client"; import openSocket from 'socket.io-client'
import { makeStyles } from "@material-ui/core/styles"; import { makeStyles } from '@material-ui/core/styles'
import Paper from "@material-ui/core/Paper"; import Paper from '@material-ui/core/Paper'
import Typography from "@material-ui/core/Typography"; import Typography from '@material-ui/core/Typography'
import Container from "@material-ui/core/Container"; import Container from '@material-ui/core/Container'
import Select from "@material-ui/core/Select"; import Select from '@material-ui/core/Select'
import { toast } from "react-toastify"; import { toast } from 'react-toastify'
import api from "../../services/api"; import api from '../../services/api'
import { i18n } from "../../translate/i18n.js"; import { i18n } from '../../translate/i18n.js'
import toastError from "../../errors/toastError"; import toastError from '../../errors/toastError'
//-------- //--------
import { AuthContext } from "../../context/Auth/AuthContext"; import { AuthContext } from '../../context/Auth/AuthContext'
import { Can } from "../../components/Can";
import { Can } from '../../components/Can'
// import Button from "@material-ui/core/Button"; // import Button from "@material-ui/core/Button";
const useStyles = makeStyles(theme => ({ const useStyles = makeStyles((theme) => ({
root: { root: {
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
padding: theme.spacing(4), padding: theme.spacing(4),
}, },
paper: { paper: {
padding: theme.spacing(2), padding: theme.spacing(2),
display: "flex", display: 'flex',
alignItems: "center", alignItems: 'center',
}, },
settingOption: { settingOption: {
marginLeft: "auto", marginLeft: 'auto',
}, },
margin: { margin: {
margin: theme.spacing(1), margin: theme.spacing(1),
}, },
})); }))
const Settings = () => { const Settings = () => {
const classes = useStyles(); const classes = useStyles()
//-------- //--------
const { user } = useContext(AuthContext); const { user } = useContext(AuthContext)
const [settings, setSettings] = useState([]); const [settings, setSettings] = useState([])
useEffect(() => { useEffect(() => {
const fetchSession = async () => { const fetchSession = async () => {
try { try {
const { data } = await api.get("/settings"); const { data } = await api.get('/settings')
setSettings(data); setSettings(data)
} catch (err) { } catch (err) {
toastError(err); toastError(err)
} }
}; }
fetchSession(); fetchSession()
}, []); }, [])
useEffect(() => { useEffect(() => {
const socket = openSocket(process.env.REACT_APP_BACKEND_URL); const socket = openSocket(process.env.REACT_APP_BACKEND_URL)
socket.on("settings", data => { socket.on('settings', (data) => {
if (data.action === "update") { console.log('settings updated ----------------------------')
setSettings(prevState => {
const aux = [...prevState]; if (data.action === 'update') {
const settingIndex = aux.findIndex(s => s.key === data.setting.key); setSettings((prevState) => {
aux[settingIndex].value = data.setting.value; const aux = [...prevState]
return aux; const settingIndex = aux.findIndex((s) => s.key === data.setting.key)
}); aux[settingIndex].value = data.setting.value
return aux
})
} }
}); })
return () => { return () => {
socket.disconnect(); socket.disconnect()
}; }
}, []); }, [])
const handleChangeSetting = async e => { useEffect(() => {
const selectedValue = e.target.value; console.log('------> settings: ', settings)
const settingKey = e.target.name; }, [settings])
const handleChangeSetting = async (e) => {
const selectedValue = e.target.value
const settingKey = e.target.name
try { try {
await api.put(`/settings/${settingKey}`, { await api.put(`/settings/${settingKey}`, {
value: selectedValue, value: selectedValue,
}); })
toast.success(i18n.t("settings.success")); toast.success(i18n.t('settings.success'))
} catch (err) { } catch (err) {
toastError(err); toastError(err)
}
} }
};
const getSettingValue = key => { const getSettingValue = (key) => {
const { value } = settings.find(s => s.key === key); const { value } = settings.find((s) => s.key === key)
return value;
};
return value
// const handleEdit = () => { }
//
// }
return ( return (
<Can <Can
role={user.profile} role={user.profile}
perform="settings-view:show" perform="settings-view:show"
yes={() => ( yes={() => (
<div> <div>
<div className={classes.root}> <div className={classes.root}>
<Container className={classes.container} maxWidth="sm"> <Container className={classes.container} maxWidth="sm">
<Typography variant="body2" gutterBottom> <Typography variant="body2" gutterBottom>
{i18n.t("settings.title")} {i18n.t('settings.title')}
</Typography> </Typography>
<Paper className={classes.paper}> <Paper className={classes.paper}>
<Typography variant="body1"> <Typography variant="body1">
{i18n.t("settings.settings.userCreation.name")} {i18n.t('settings.settings.userCreation.name')}
</Typography> </Typography>
<Select <Select
@ -132,59 +129,79 @@ const Settings = () => {
id="userCreation-setting" id="userCreation-setting"
name="userCreation" name="userCreation"
value={ value={
settings && settings.length > 0 && getSettingValue("userCreation") settings &&
settings.length > 0 &&
getSettingValue('userCreation')
} }
className={classes.settingOption} className={classes.settingOption}
onChange={handleChangeSetting} onChange={handleChangeSetting}
> >
<option value="enabled"> <option value="enabled">
{i18n.t("settings.settings.userCreation.options.enabled")} {i18n.t('settings.settings.userCreation.options.enabled')}
</option> </option>
<option value="disabled"> <option value="disabled">
{i18n.t("settings.settings.userCreation.options.disabled")} {i18n.t('settings.settings.userCreation.options.disabled')}
</option> </option>
</Select> </Select>
</Paper> </Paper>
</Container> </Container>
</div> </div>
<div className={classes.root}>
{/* <div className={classes.root}>
<Container className={classes.container} maxWidth="sm"> <Container className={classes.container} maxWidth="sm">
<Typography variant="body2" gutterBottom>
Application name
</Typography>
<Paper className={classes.paper}> <Paper className={classes.paper}>
<Typography variant="body1">Editar ura</Typography>
<Typography variant="body1"> <Select
Estudio Face
</Typography>
<Button
margin="dense" margin="dense"
variant="outlined" variant="outlined"
id="applicationName-setting" native
name="applicationName" id="editURA-setting"
color="primary" name="editURA"
onClick={(e) => handleEdit()} value={
settings &&
settings.length > 0 &&
getSettingValue('editURA')
}
className={classes.settingOption} className={classes.settingOption}
onChange={handleChangeSetting}
> >
{"EDIT"} <option value="enabled">Ativado</option>
</Button> <option value="disabled">Desativado</option>
</Select>
</Paper> </Paper>
</Container> </Container>
</div> */}
</div> </div>
<div className={classes.root}>
<Container className={classes.container} maxWidth="sm">
<Paper className={classes.paper}>
<Typography variant="body1">Editar fila</Typography>
<Select
margin="dense"
variant="outlined"
native
id="editQueue-setting"
name="editQueue"
value={
settings &&
settings.length > 0 &&
getSettingValue('editQueue')
}
className={classes.settingOption}
onChange={handleChangeSetting}
>
<option value="enabled">Ativado</option>
<option value="disabled">Desativado</option>
</Select>
</Paper>
</Container>
</div>
</div>
)} )}
/> />
)
}
); export default Settings
};
export default Settings;

View File

@ -5,55 +5,54 @@ const rules = {
admin: { admin: {
static: [ static: [
//"show-icon-edit-whatsapp", 'show-icon-edit-whatsapp',
'show-icon-edit-queue',
"drawer-admin-items:view",
"tickets-manager:showall",
"user-modal:editProfile",
"user-modal:editQueues",
"ticket-options:deleteTicket",
"contacts-page:deleteContact",
"contacts-page:import-csv-contacts",
"connections-view:show",
"dashboard-view:show",
"queues-view:show",
"user-view:show",
"ticket-report:show",
'drawer-admin-items:view',
'tickets-manager:showall',
'user-modal:editProfile',
'user-modal:editQueues',
'ticket-options:deleteTicket',
'contacts-page:deleteContact',
'contacts-page:import-csv-contacts',
'connections-view:show',
'dashboard-view:show',
'queues-view:show',
'user-view:show',
'ticket-report:show',
], ],
}, },
master: { master: {
static: [ static: [
'url-remote-session:show',
'show-icon-edit-whatsapp',
'show-icon-add-queue',
'show-icon-edit-queue',
'show-icon-delete-queue',
'space-disk-info:show',
"show-icon-edit-whatsapp", 'drawer-admin-items:view',
"show-icon-add-queue", 'tickets-manager:showall',
"show-icon-edit-queue", 'user-modal:editProfile',
"show-icon-delete-queue", 'user-modal:editQueues',
"space-disk-info:show", 'ticket-options:deleteTicket',
'contacts-page:deleteContact',
'contacts-page:import-contacts',
"drawer-admin-items:view", 'contacts-page:import-csv-contacts',
"tickets-manager:showall", 'connections-view:show',
"user-modal:editProfile", 'dashboard-view:show',
"user-modal:editQueues", 'queues-view:show',
"ticket-options:deleteTicket", 'user-view:show',
"contacts-page:deleteContact", 'settings-view:show',
"contacts-page:import-contacts", 'btn-add-user',
"contacts-page:import-csv-contacts", 'icon-remove-user',
"connections-view:show", 'btn-add-whatsapp',
"dashboard-view:show", 'btn-remove-whatsapp',
"queues-view:show", 'ticket-report:show',
"user-view:show", 'connection-button:show',
"settings-view:show",
"btn-add-user",
"icon-remove-user",
"btn-add-whatsapp",
"btn-remove-whatsapp",
"ticket-report:show",
"connection-button:show"
], ],
}, },
}; }
export default rules; export default rules