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";
export const index = async (req: Request, res: Response): Promise<Response> => {
if (req.user.profile !== "master") {
throw new AppError("ERR_NO_PERMISSION", 403);
}
// if (req.user.profile !== "master") {
// throw new AppError("ERR_NO_PERMISSION", 403);
// }
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();
settingRoutes.get("/settings", isAuth, SettingController.index);
settingRoutes.get("/settings", SettingController.index);
// routes.get("/settings/:settingKey", isAuth, SettingsController.show);

View File

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

View File

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

View File

@ -1,290 +1,300 @@
import React, { useState, useEffect } from "react";
import * as Yup from "yup";
import { Formik, Form, Field } from "formik";
import { toast } from "react-toastify";
import React, { useState, useEffect, useContext } from 'react'
import * as Yup from 'yup'
import { Formik, Form, Field } from 'formik'
import { toast } from 'react-toastify'
import { makeStyles } from "@material-ui/core/styles";
import { green } from "@material-ui/core/colors";
import { makeStyles } from '@material-ui/core/styles'
import { green } from '@material-ui/core/colors'
import { AuthContext } from '../../context/Auth/AuthContext'
import { Can } from '../../components/Can'
import {
Dialog,
DialogContent,
DialogTitle,
Button,
DialogActions,
CircularProgress,
TextField,
Switch,
FormControlLabel,
} from "@material-ui/core";
Dialog,
DialogContent,
DialogTitle,
Button,
DialogActions,
CircularProgress,
TextField,
Switch,
FormControlLabel,
} from '@material-ui/core'
import api from "../../services/api";
import { i18n } from "../../translate/i18n";
import toastError from "../../errors/toastError";
import QueueSelect from "../QueueSelect";
import api from '../../services/api'
import { i18n } from '../../translate/i18n'
import toastError from '../../errors/toastError'
import QueueSelect from '../QueueSelect'
const useStyles = makeStyles(theme => ({
root: {
display: "flex",
flexWrap: "wrap",
},
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
flexWrap: 'wrap',
},
multFieldLine: {
display: "flex",
"& > *:not(:last-child)": {
marginRight: theme.spacing(1),
},
},
multFieldLine: {
display: 'flex',
'& > *:not(:last-child)': {
marginRight: theme.spacing(1),
},
},
btnWrapper: {
position: "relative",
},
btnWrapper: {
position: 'relative',
},
buttonProgress: {
color: green[500],
position: "absolute",
top: "50%",
left: "50%",
marginTop: -12,
marginLeft: -12,
},
}));
buttonProgress: {
color: green[500],
position: 'absolute',
top: '50%',
left: '50%',
marginTop: -12,
marginLeft: -12,
},
}))
const SessionSchema = Yup.object().shape({
name: Yup.string()
.min(2, "Too Short!")
.max(100, "Too Long!")
.required("Required"),
});
name: Yup.string()
.min(2, 'Too Short!')
.max(100, 'Too Long!')
.required('Required'),
})
const WhatsAppModal = ({ open, onClose, whatsAppId }) => {
const classes = useStyles();
const initialState = {
name: "",
urlApi: "",
url: "",
greetingMessage: "",
farewellMessage: "",
isDefault: false,
};
const [whatsApp, setWhatsApp] = useState(initialState);
const [selectedQueueIds, setSelectedQueueIds] = useState([]);
const classes = useStyles()
const initialState = {
name: '',
urlApi: '',
url: '',
greetingMessage: '',
farewellMessage: '',
isDefault: false,
}
useEffect(() => {
const fetchSession = async () => {
if (!whatsAppId) return;
const { user } = useContext(AuthContext)
try {
const { data } = await api.get(`whatsapp/${whatsAppId}`);
setWhatsApp(data);
const [whatsApp, setWhatsApp] = useState(initialState)
const [selectedQueueIds, setSelectedQueueIds] = useState([])
const whatsQueueIds = data.queues?.map(queue => queue.id);
setSelectedQueueIds(whatsQueueIds);
} catch (err) {
toastError(err);
}
};
fetchSession();
}, [whatsAppId]);
useEffect(() => {
const fetchSession = async () => {
if (!whatsAppId) return
const handleSaveWhatsApp = async values => {
const whatsappData = { ...values, queueIds: selectedQueueIds };
try {
const { data } = await api.get(`whatsapp/${whatsAppId}`)
setWhatsApp(data)
let response = null
const whatsQueueIds = data.queues?.map((queue) => queue.id)
setSelectedQueueIds(whatsQueueIds)
} catch (err) {
toastError(err)
}
}
fetchSession()
}, [whatsAppId])
try {
if (whatsAppId) {
response = await api.put(`/whatsapp/${whatsAppId}`, whatsappData);
} else {
response = await api.post("/whatsapp", whatsappData);
}
const handleSaveWhatsApp = async (values) => {
const whatsappData = { ...values, queueIds: selectedQueueIds }
console.log('response: ', response.data.message)
let response = null
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!')
}
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) {
toastError(err);
}
};
try {
if (whatsAppId) {
response = await api.put(`/whatsapp/${whatsAppId}`, whatsappData)
} else {
response = await api.post('/whatsapp', whatsappData)
}
const handleClose = () => {
onClose();
setWhatsApp(initialState);
};
console.log('response: ', response.data.message)
return (
<div className={classes.root}>
<Dialog
open={open}
onClose={handleClose}
maxWidth="sm"
fullWidth
scroll="paper"
>
<DialogTitle>
{whatsAppId
? i18n.t("whatsappModal.title.edit")
: i18n.t("whatsappModal.title.add")}
</DialogTitle>
<Formik
initialValues={whatsApp}
enableReinitialize={true}
validationSchema={SessionSchema}
onSubmit={(values, actions) => {
setTimeout(() => {
handleSaveWhatsApp(values);
actions.setSubmitting(false);
}, 400);
}}
>
{({ values, touched, errors, isSubmitting }) => (
<Form>
<DialogContent dividers>
<div className={classes.multFieldLine}>
<Field
as={TextField}
label={i18n.t("whatsappModal.form.name")}
autoFocus
name="name"
error={touched.name && Boolean(errors.name)}
helperText={touched.name && errors.name}
variant="outlined"
margin="dense"
className={classes.textField}
/>
<FormControlLabel
control={
<Field
as={Switch}
color="primary"
name="isDefault"
checked={values.isDefault}
/>
}
label={i18n.t("whatsappModal.form.default")}
/>
</div>
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!'
)
} 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) {
toastError(err)
}
}
const handleClose = () => {
onClose()
setWhatsApp(initialState)
}
return (
<div className={classes.root}>
<Dialog
open={open}
onClose={handleClose}
maxWidth="sm"
fullWidth
scroll="paper"
>
<DialogTitle>
{whatsAppId
? i18n.t('whatsappModal.title.edit')
: i18n.t('whatsappModal.title.add')}
</DialogTitle>
<Formik
initialValues={whatsApp}
enableReinitialize={true}
validationSchema={SessionSchema}
onSubmit={(values, actions) => {
setTimeout(() => {
handleSaveWhatsApp(values)
actions.setSubmitting(false)
}, 400)
}}
>
{({ values, touched, errors, isSubmitting }) => (
<Form>
<DialogContent dividers>
<Can
role={user.profile}
perform="url-remote-session:show"
yes={() => (
<>
<div className={classes.multFieldLine}>
<Field
as={TextField}
label={i18n.t('whatsappModal.form.name')}
autoFocus
name="name"
error={touched.name && Boolean(errors.name)}
helperText={touched.name && errors.name}
variant="outlined"
margin="dense"
className={classes.textField}
/>
<FormControlLabel
control={
<Field
as={Switch}
color="primary"
name="isDefault"
checked={values.isDefault}
/>
}
label={i18n.t('whatsappModal.form.default')}
/>
</div>
<div className={classes.multFieldLine}>
<Field
as={TextField}
label="url API"
autoFocus
name="urlApi"
error={touched.name && Boolean(errors.name)}
helperText={touched.name && errors.name}
variant="outlined"
margin="dense"
className={classes.textField}
/>
<Field
as={TextField}
label="url session"
autoFocus
name="url"
error={touched.name && Boolean(errors.name)}
helperText={touched.name && errors.name}
variant="outlined"
margin="dense"
className={classes.textField}
/>
</div>
</>
)}
/>
<div className={classes.multFieldLine}>
<Field
as={TextField}
label='url API'
autoFocus
name="urlApi"
error={touched.name && Boolean(errors.name)}
helperText={touched.name && errors.name}
variant="outlined"
margin="dense"
className={classes.textField}
/>
<Field
as={TextField}
label='url session'
autoFocus
name="url"
error={touched.name && Boolean(errors.name)}
helperText={touched.name && errors.name}
variant="outlined"
margin="dense"
className={classes.textField}
/>
</div>
<div>
<Field
as={TextField}
label={i18n.t('queueModal.form.greetingMessage')}
type="greetingMessage"
multiline
rows={5}
fullWidth
name="greetingMessage"
error={
touched.greetingMessage && Boolean(errors.greetingMessage)
}
helperText={
touched.greetingMessage && errors.greetingMessage
}
variant="outlined"
margin="dense"
/>
</div>
<div>
<Field
as={TextField}
label={i18n.t('whatsappModal.form.farewellMessage')}
type="farewellMessage"
multiline
rows={5}
fullWidth
name="farewellMessage"
error={
touched.farewellMessage && Boolean(errors.farewellMessage)
}
helperText={
touched.farewellMessage && errors.farewellMessage
}
variant="outlined"
margin="dense"
/>
</div>
<QueueSelect
selectedQueueIds={selectedQueueIds}
onChange={(selectedIds) => setSelectedQueueIds(selectedIds)}
/>
</DialogContent>
<DialogActions>
<Button
onClick={handleClose}
color="secondary"
disabled={isSubmitting}
variant="outlined"
>
{i18n.t('whatsappModal.buttons.cancel')}
</Button>
<Button
type="submit"
color="primary"
disabled={isSubmitting}
variant="contained"
className={classes.btnWrapper}
>
{whatsAppId
? i18n.t('whatsappModal.buttons.okEdit')
: i18n.t('whatsappModal.buttons.okAdd')}
{isSubmitting && (
<CircularProgress
size={24}
className={classes.buttonProgress}
/>
)}
</Button>
</DialogActions>
</Form>
)}
</Formik>
</Dialog>
</div>
)
}
<div>
<Field
as={TextField}
label={i18n.t("queueModal.form.greetingMessage")}
type="greetingMessage"
multiline
rows={5}
fullWidth
name="greetingMessage"
error={
touched.greetingMessage && Boolean(errors.greetingMessage)
}
helperText={
touched.greetingMessage && errors.greetingMessage
}
variant="outlined"
margin="dense"
/>
</div>
<div>
<Field
as={TextField}
label={i18n.t("whatsappModal.form.farewellMessage")}
type="farewellMessage"
multiline
rows={5}
fullWidth
name="farewellMessage"
error={
touched.farewellMessage && Boolean(errors.farewellMessage)
}
helperText={
touched.farewellMessage && errors.farewellMessage
}
variant="outlined"
margin="dense"
/>
</div>
<QueueSelect
selectedQueueIds={selectedQueueIds}
onChange={selectedIds => setSelectedQueueIds(selectedIds)}
/>
</DialogContent>
<DialogActions>
<Button
onClick={handleClose}
color="secondary"
disabled={isSubmitting}
variant="outlined"
>
{i18n.t("whatsappModal.buttons.cancel")}
</Button>
<Button
type="submit"
color="primary"
disabled={isSubmitting}
variant="contained"
className={classes.btnWrapper}
>
{whatsAppId
? i18n.t("whatsappModal.buttons.okEdit")
: i18n.t("whatsappModal.buttons.okAdd")}
{isSubmitting && (
<CircularProgress
size={24}
className={classes.buttonProgress}
/>
)}
</Button>
</DialogActions>
</Form>
)}
</Formik>
</Dialog>
</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 { loading, user, isAuth, handleLogin, handleLogout } = useAuth();
const { loading, user, isAuth, handleLogin, handleLogout, setSetting } =
useAuth()
//{
//{
return (
<AuthContext.Provider
value={{ loading, user, isAuth, handleLogin, handleLogout }}
>
{children}
</AuthContext.Provider>
);
};
return (
<AuthContext.Provider
value={{ loading, user, isAuth, handleLogin, handleLogout, setSetting }}
>
{children}
</AuthContext.Provider>
)
}
export { AuthContext, AuthProvider };
export { AuthContext, AuthProvider }

View File

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

File diff suppressed because it is too large Load Diff

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

View File

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

View File

@ -1,59 +1,58 @@
const rules = {
user: {
static: [],
},
user: {
static: [],
},
admin: {
static: [
//"show-icon-edit-whatsapp",
admin: {
static: [
'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: {
static: [
master: {
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",
"show-icon-add-queue",
"show-icon-edit-queue",
"show-icon-delete-queue",
"space-disk-info:show",
'drawer-admin-items:view',
'tickets-manager:showall',
'user-modal:editProfile',
'user-modal:editQueues',
'ticket-options:deleteTicket',
'contacts-page:deleteContact',
'contacts-page:import-contacts',
'contacts-page:import-csv-contacts',
'connections-view:show',
'dashboard-view:show',
'queues-view:show',
'user-view:show',
'settings-view:show',
'btn-add-user',
'icon-remove-user',
'btn-add-whatsapp',
'btn-remove-whatsapp',
'ticket-report:show',
'connection-button:show',
],
},
}
"drawer-admin-items:view",
"tickets-manager:showall",
"user-modal:editProfile",
"user-modal:editQueues",
"ticket-options:deleteTicket",
"contacts-page:deleteContact",
"contacts-page:import-contacts",
"contacts-page:import-csv-contacts",
"connections-view:show",
"dashboard-view:show",
"queues-view:show",
"user-view: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