diff --git a/frontend/src/components/CampaignModal/index.js b/frontend/src/components/CampaignModal/index.js
new file mode 100644
index 0000000..ba15420
--- /dev/null
+++ b/frontend/src/components/CampaignModal/index.js
@@ -0,0 +1,346 @@
+import React, { useState, useEffect, useContext, createContext } 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 { AuthContext } from '../../context/Auth/AuthContext'
+import { Can } from '../../components/Can'
+
+import apiBroker from '../../services/apiBroker'
+
+import Select from "@material-ui/core/Select"
+import MenuItem from "@material-ui/core/MenuItem";
+
+import SelectField from "../../components/Report/SelectField"
+
+import { WhatsAppsContext } from "../../context/WhatsApp/WhatsAppsContext";
+
+import {
+ 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'
+
+const useStyles = makeStyles((theme) => ({
+ root: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+
+ multFieldLine: {
+ display: 'flex',
+ '& > *:not(:last-child)': {
+ marginRight: theme.spacing(1),
+ },
+ },
+
+ btnWrapper: {
+ position: 'relative',
+ },
+
+ 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'),
+})
+
+const CampaignModal = ({ open, onClose, campaignId, dispatch }) => {
+ const classes = useStyles()
+ const initialState = {
+ name: '',
+ message: '',
+ whatsapp_sender: '',
+ csv_original_file_name: '',
+ }
+
+ const { user } = useContext(AuthContext)
+
+ const { whatsApps } = useContext(WhatsAppsContext);
+
+ console.log('------------> whatsApps: ', whatsApps)
+
+ const [campaign, setCampaign] = useState(initialState)
+ // const [selectedQueueIds, setSelectedQueueIds] = useState([])
+ const [file, setFile] = useState()
+
+ const [selectedNumber, setSelectedNumber] = useState('');
+
+ const [itemHover, setItemHover] = useState(-1)
+
+ useEffect(() => {
+ console.log('selectedNumber: ', selectedNumber)
+ }, [selectedNumber])
+
+
+
+ useEffect(() => {
+ const fetchSession = async () => {
+ if (!campaignId) return
+
+ try {
+
+ const { data } = await apiBroker.get('/campaign', {
+ params: {
+ adminId: user.id,
+ baseURL: process.env.REACT_APP_BACKEND_URL_PRIVATE,
+ identifier: 'campaign',
+ id: campaignId
+ }
+ })
+
+ setCampaign(data.campaign)
+ setSelectedNumber(data.campaign.whatsapp_sender)
+
+ } catch (err) {
+ toastError(err)
+ }
+ }
+ fetchSession()
+ }, [campaignId])
+
+ const handleSaveCampaign = async (values) => {
+ let response = null
+
+ const formData = new FormData()
+ formData.append('adminId', user.id)
+ formData.append('baseURL', process.env.REACT_APP_BACKEND_URL_PRIVATE)
+ formData.append('frontURL', process.env.REACT_APP_FRONTEND_URL)
+ formData.append('identifier', 'campaign')
+ formData.append('file', file)
+ formData.append('name', values.name)
+ formData.append('whatsapp_sender', selectedNumber)
+ formData.append('message', values.message)
+ formData.append('csv_original_file_name', file?.name)
+
+
+ const config = {
+ headers: {
+ 'content-type': 'multipart/form-data',
+ },
+ }
+
+ try {
+ if (campaignId) {
+
+ response = await apiBroker.patch(`/campaign/${campaignId}`,
+ formData,
+ config
+ )
+ toast.success('Campanha atualizada com sucesso!')
+
+ } else {
+
+ response = await apiBroker.post('/campaign',
+ formData,
+ config
+ )
+ toast.success('Campanha criada com sucesso!')
+ }
+
+ dispatch({ type: "UPDATE_CAMPAIGNS", payload: response.data.campaign })
+
+ handleClose()
+
+ } catch (err) {
+ toastError(err)
+ }
+ }
+
+ const handleClose = () => {
+ onClose()
+ setCampaign(initialState)
+ setSelectedNumber('')
+ }
+
+ async function handleChange(event) {
+ try {
+ if (event.target.files[0].size > 1024 * 1024 * 4) {
+ alert('Arquivo não pode ser maior que 4 MB!')
+ return
+ }
+
+ console.log('event.target.files[0]: ', event.target.files[0])
+ setFile(event.target.files[0])
+
+ } catch (err) {
+ toastError(err)
+ }
+ }
+
+ return (
+
+
+
+ )
+}
+
+export default React.memo(CampaignModal)
diff --git a/frontend/src/layout/MainListItems.js b/frontend/src/layout/MainListItems.js
index 1455c45..0795b9a 100644
--- a/frontend/src/layout/MainListItems.js
+++ b/frontend/src/layout/MainListItems.js
@@ -1,40 +1,42 @@
-import React, { useContext, useEffect, useState } from "react";
-import { Link as RouterLink } from "react-router-dom";
+import React, { useContext, useEffect, useState } from 'react'
+import { Link as RouterLink } from 'react-router-dom'
-import ListItem from "@material-ui/core/ListItem";
-import ListItemIcon from "@material-ui/core/ListItemIcon";
-import ListItemText from "@material-ui/core/ListItemText";
-import ListSubheader from "@material-ui/core/ListSubheader";
-import Divider from "@material-ui/core/Divider";
-import { Badge } from "@material-ui/core";
-import DashboardOutlinedIcon from "@material-ui/icons/DashboardOutlined";
+import ListItem from '@material-ui/core/ListItem'
+import ListItemIcon from '@material-ui/core/ListItemIcon'
+import ListItemText from '@material-ui/core/ListItemText'
+import ListSubheader from '@material-ui/core/ListSubheader'
+import Divider from '@material-ui/core/Divider'
+import { Badge } from '@material-ui/core'
+import DashboardOutlinedIcon from '@material-ui/icons/DashboardOutlined'
-import ReportOutlinedIcon from "@material-ui/icons/ReportOutlined";
-import SendOutlined from "@material-ui/icons/SendOutlined";
+import ReportOutlinedIcon from '@material-ui/icons/ReportOutlined'
+import SendOutlined from '@material-ui/icons/SendOutlined'
//import ReportOutlined from "@bit/mui-org.material-ui-icons.report-outlined";
+import WhatsAppIcon from '@material-ui/icons/WhatsApp'
+import SyncAltIcon from '@material-ui/icons/SyncAlt'
+import SettingsOutlinedIcon from '@material-ui/icons/SettingsOutlined'
+import PeopleAltOutlinedIcon from '@material-ui/icons/PeopleAltOutlined'
+import ContactPhoneOutlinedIcon from '@material-ui/icons/ContactPhoneOutlined'
+import AccountTreeOutlinedIcon from '@material-ui/icons/AccountTreeOutlined'
+import QuestionAnswerOutlinedIcon from '@material-ui/icons/QuestionAnswerOutlined'
-import WhatsAppIcon from "@material-ui/icons/WhatsApp";
-import SyncAltIcon from "@material-ui/icons/SyncAlt";
-import SettingsOutlinedIcon from "@material-ui/icons/SettingsOutlined";
-import PeopleAltOutlinedIcon from "@material-ui/icons/PeopleAltOutlined";
-import ContactPhoneOutlinedIcon from "@material-ui/icons/ContactPhoneOutlined";
-import AccountTreeOutlinedIcon from "@material-ui/icons/AccountTreeOutlined";
-import QuestionAnswerOutlinedIcon from "@material-ui/icons/QuestionAnswerOutlined";
-
-import { i18n } from "../translate/i18n";
-import { WhatsAppsContext } from "../context/WhatsApp/WhatsAppsContext";
-import { AuthContext } from "../context/Auth/AuthContext";
-import { Can } from "../components/Can";
+import { i18n } from '../translate/i18n'
+import { WhatsAppsContext } from '../context/WhatsApp/WhatsAppsContext'
+import { AuthContext } from '../context/Auth/AuthContext'
+import { Can } from '../components/Can'
function ListItemLink(props) {
- const { icon, primary, to, className } = props;
+ const { icon, primary, to, className } = props
const renderLink = React.useMemo(
- () => React.forwardRef((itemProps, ref) => ),
+ () =>
+ React.forwardRef((itemProps, ref) => (
+
+ )),
[to]
- );
+ )
return (
@@ -43,55 +45,59 @@ function ListItemLink(props) {
- );
+ )
}
const MainListItems = (props) => {
- const { setDrawerOpen } = props;
- const { whatsApps } = useContext(WhatsAppsContext);
- const { user } = useContext(AuthContext);
- const [connectionWarning, setConnectionWarning] = useState(false);
+ const { setDrawerOpen } = props
+ const { whatsApps } = useContext(WhatsAppsContext)
+ const { user } = useContext(AuthContext)
+ const [connectionWarning, setConnectionWarning] = useState(false)
useEffect(() => {
const delayDebounceFn = setTimeout(() => {
if (whatsApps.length > 0) {
const offlineWhats = whatsApps.filter((whats) => {
return (
- whats.status === "qrcode" ||
- whats.status === "PAIRING" ||
- whats.status === "DISCONNECTED" ||
- whats.status === "TIMEOUT" ||
- whats.status === "OPENING"
- );
- });
+ whats.status === 'qrcode' ||
+ whats.status === 'PAIRING' ||
+ whats.status === 'DISCONNECTED' ||
+ whats.status === 'TIMEOUT' ||
+ whats.status === 'OPENING'
+ )
+ })
if (offlineWhats.length > 0) {
- setConnectionWarning(true);
+ setConnectionWarning(true)
} else {
- setConnectionWarning(false);
+ setConnectionWarning(false)
}
}
- }, 2000);
- return () => clearTimeout(delayDebounceFn);
- }, [whatsApps]);
+ }, 2000)
+ return () => clearTimeout(delayDebounceFn)
+ }, [whatsApps])
return (
//Solicitado pelo Adriano: Click no LinkItem e fechar o menu!
setDrawerOpen(false)}>
}
/>
}
/>
-
} />
+
}
+ />
}
/>
{
yes={() => (
<>
- {i18n.t("mainDrawer.listItems.administration")}
+
+ {i18n.t('mainDrawer.listItems.administration')}
+
}
/>
}
/>
+
}
/>
- } />
+ }
+ />
- } />
+ }
+ />
+
+ }
+ />
{
yes={() => (
}
/>
)}
@@ -141,7 +163,7 @@ const MainListItems = (props) => {
)}
/>
- );
-};
+ )
+}
-export default MainListItems;
+export default MainListItems
diff --git a/frontend/src/pages/Campaign/index.js b/frontend/src/pages/Campaign/index.js
new file mode 100644
index 0000000..54e5e42
--- /dev/null
+++ b/frontend/src/pages/Campaign/index.js
@@ -0,0 +1,696 @@
+import React, { useState, useCallback, useEffect, useReducer, useContext } from 'react'
+import { toast } from 'react-toastify'
+import { format, parseISO } from 'date-fns'
+
+import openSocket from 'socket.io-client'
+
+import { makeStyles } from '@material-ui/core/styles'
+import { green } from '@material-ui/core/colors'
+import {
+ Button,
+ TableBody,
+ TableRow,
+ TableCell,
+ IconButton,
+ Table,
+ TableHead,
+ Paper,
+ Tooltip,
+ Typography,
+ CircularProgress,
+} from '@material-ui/core'
+import {
+ Edit,
+ CheckCircle,
+ SignalCellularConnectedNoInternet2Bar,
+ SignalCellularConnectedNoInternet0Bar,
+ SignalCellular4Bar,
+ CropFree,
+ DeleteOutline,
+ // Restore
+} from '@material-ui/icons'
+
+import MainContainer from '../../components/MainContainer'
+import MainHeader from '../../components/MainHeader'
+import MainHeaderButtonsWrapper from '../../components/MainHeaderButtonsWrapper'
+import Title from '../../components/Title'
+import TableRowSkeleton from '../../components/TableRowSkeleton'
+
+import api from '../../services/api'
+import CampaignModal from '../../components/CampaignModal'
+import ConfirmationModal from '../../components/ConfirmationModal'
+import QrcodeModal from '../../components/QrcodeModal'
+import { i18n } from '../../translate/i18n'
+// import { WhatsAppsContext } from '../../context/WhatsApp/WhatsAppsContext'
+import toastError from '../../errors/toastError'
+
+//--------
+import { AuthContext } from '../../context/Auth/AuthContext'
+import { Can } from '../../components/Can'
+
+import apiBroker from '../../services/apiBroker'
+
+
+
+
+const reducer = (state, action) => {
+
+
+ if (action.type === "LOAD_CAMPAIGNS") {
+
+ const campaigns = action.payload
+ return [...state, ...campaigns]
+ }
+ if (action.type === "UPDATE_CAMPAIGNS") {
+
+ console.log('STATE: ', state)
+
+ const campaign = action.payload
+ const campaignIndex = state.findIndex((c) => c.id === campaign.id)
+
+ if (campaignIndex !== -1) {
+ state[campaignIndex] = campaign
+ return [...state]
+ } else {
+ return [campaign, ...state]
+ }
+ }
+ if (action.type === "DELETE_CAMPAIGN") {
+ const campaignId = action.payload
+
+ const campaignIndex = state.findIndex((c) => c.id === campaignId)
+ if (campaignIndex !== -1) {
+ state.splice(campaignIndex, 1)
+ }
+ return [...state]
+ }
+
+ if (action.type === "RESET") {
+ return []
+ }
+}
+
+const useStyles = makeStyles((theme) => ({
+ mainPaper: {
+ flex: 1,
+ padding: theme.spacing(1),
+ overflowY: 'scroll',
+ ...theme.scrollbarStyles,
+ },
+ customTableCell: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ tooltip: {
+ backgroundColor: '#f5f5f9',
+ color: 'rgba(0, 0, 0, 0.87)',
+ fontSize: theme.typography.pxToRem(14),
+ border: '1px solid #dadde9',
+ maxWidth: 450,
+ },
+ tooltipPopper: {
+ textAlign: 'center',
+ },
+ buttonProgress: {
+ color: green[500],
+ },
+}))
+
+const CustomToolTip = ({ title, content, children }) => {
+ const classes = useStyles()
+
+ return (
+
+
+ {title}
+
+ {content && {content}}
+
+ }
+ >
+ {children}
+
+ )
+}
+
+const Campaign = () => {
+ //--------
+ const { user } = useContext(AuthContext)
+
+ const classes = useStyles()
+
+ // const { whatsApps } = useContext(WhatsAppsContext)
+
+ // console.log('------------> whatsApps: ', whatsApps)
+
+ // const { campaigns, loading } = useContext(WhatsAppsContext)
+ const [campaignModalOpen, setCampaignModalOpen] = useState(false)
+ const [qrModalOpen, setQrModalOpen] = useState(false)
+ const [selectedCampaign, setSelectedCampaign] = useState(null)
+ const [confirmModalOpen, setConfirmModalOpen] = useState(false)
+
+ const [diskSpaceInfo, setDiskSpaceInfo] = useState({})
+
+ const [disabled, setDisabled] = useState(true)
+
+ const [settings, setSettings] = useState([])
+
+ const [buttons, setClicks] = useState([])
+
+ const [campaigns, dispatch] = useReducer(reducer, [])
+
+ const [loading, setLoading] = useState(true)
+
+ const confirmationModalInitialState = {
+ action: '',
+ title: '',
+ message: '',
+ campaignId: '',
+ csv_original_file_name:'',
+ open: false,
+ }
+ const [confirmModalInfo, setConfirmModalInfo] = useState(
+ confirmationModalInitialState
+ )
+
+
+ useEffect(() => {
+ dispatch({ type: "RESET" })
+ }, [])
+
+ useEffect(() => {
+ setLoading(true)
+
+ const delayDebounceFn = setTimeout(() => {
+ const fetchContacts = async () => {
+
+ try {
+ console.log('process.env.REACT_APP_BACKEND_URL_PRIVATE: ', process.env.REACT_APP_BACKEND_URL_PRIVATE)
+
+ const { data } = await apiBroker.get('/campaign', {
+ params: {
+ adminId: user.id,
+ baseURL: process.env.REACT_APP_BACKEND_URL_PRIVATE,
+ identifier: 'campaign'
+ }
+ })
+
+ dispatch({ type: "LOAD_CAMPAIGNS", payload: data.campaign })
+ setLoading(false)
+
+ } catch (err) {
+ toastError(err)
+ }
+
+ }
+ fetchContacts()
+ }, 500)
+ return () => clearTimeout(delayDebounceFn)
+ }, [])
+
+ useEffect(() => {
+ const fetchSession = async () => {
+ try {
+ // const test = await apiBroker.get('/contacts/status/insert/onqueue', {
+ // params: {
+ // adminId: user.id,
+ // baseURL: process.env.REACT_APP_BACKEND_URL_PRIVATE,
+ // identifier: 'campaign',
+ // },
+ // })
+
+ // console.log('-------------------> test: ', test)
+
+ // const { data } = await api.get('/settings')
+ // setSettings(data)
+
+ } catch (err) {
+ toastError(err)
+ }
+ }
+ fetchSession()
+ }, [])
+
+ const getSettingValue = (key) => {
+ const { value } = settings.find((s) => s.key === key)
+
+ return value
+ }
+
+ const handleStartWhatsAppSession = async (campaignId) => {
+ try {
+ await api.post(`/whatsappsession/${campaignId}`)
+ } catch (err) {
+ toastError(err)
+ }
+ }
+
+ // useEffect(() => {
+ // for (let i = 0; i < campaigns.length; i++) {
+ // if (buttons.includes(campaigns[i].id)) {
+ // campaigns[i].disabled = true
+ // }
+ // }
+ // }, [campaigns, buttons])
+
+ const handleRequestNewQrCode = async (campaignId) => {
+ try {
+ await api.put(`/whatsappsession/${campaignId}`)
+ } catch (err) {
+ toastError(err)
+ }
+ }
+
+ const handleOpenCampaignModal = () => {
+ setSelectedCampaign(null)
+ setCampaignModalOpen(true)
+ }
+
+ const handleCloseCampaignModal = useCallback(() => {
+ setCampaignModalOpen(false)
+ setSelectedCampaign(null)
+ }, [setSelectedCampaign, setCampaignModalOpen])
+
+ const handleOpenQrModal = (campaign) => {
+ setSelectedCampaign(campaign)
+ setQrModalOpen(true)
+ }
+
+ const handleCloseQrModal = useCallback(() => {
+ setSelectedCampaign(null)
+ setQrModalOpen(false)
+ }, [setQrModalOpen, setSelectedCampaign])
+
+ const handleEditCampaign = (campaign) => {
+ setSelectedCampaign(campaign)
+ setCampaignModalOpen(true)
+ }
+
+ const handleOpenConfirmationModal = (action, campaignId) => {
+ if (action === 'disconnect') {
+ setConfirmModalInfo({
+ action: action,
+ title: i18n.t('connections.confirmationModal.disconnectTitle'),
+ message: i18n.t('connections.confirmationModal.disconnectMessage'),
+ campaignId: campaignId,
+ })
+ }
+
+ if (action === 'delete') {
+ setConfirmModalInfo({
+ action: action,
+ title: i18n.t('connections.confirmationModal.deleteTitle'),
+ message: i18n.t('connections.confirmationModal.deleteMessage'),
+ campaignId: campaignId,
+ })
+ }
+ setConfirmModalOpen(true)
+ }
+
+ const handleSubmitConfirmationModal = async () => {
+ if (confirmModalInfo.action === 'disconnect') {
+ try {
+ await api.delete(`/whatsappsession/${confirmModalInfo.campaignId}`)
+ } catch (err) {
+ toastError(err)
+ }
+ }
+
+ if (confirmModalInfo.action === 'delete') {
+ try {
+ await apiBroker.delete(`/campaign/${confirmModalInfo.campaignId}`, {
+ params: {
+ adminId: user.id,
+ baseURL: process.env.REACT_APP_BACKEND_URL_PRIVATE,
+ identifier: 'campaign',
+ },
+ })
+
+ dispatch({ type: "DELETE_CAMPAIGN", payload: confirmModalInfo.campaignId })
+
+ toast.success('Campanha deletada com sucesso')
+ } catch (err) {
+ toastError(err)
+ }
+ }
+
+ setConfirmModalInfo(confirmationModalInitialState)
+ }
+
+ const renderActionButtons = (campaign) => {
+ return (
+ (
+ <>
+ {campaign.status === 'qrcode' && (
+
+ )}
+ {campaign.status === 'DISCONNECTED' && (
+ <>
+ {' '}
+
+ >
+ )}
+ {(campaign.status === 'CONNECTED' ||
+ campaign.status === 'PAIRING' ||
+ campaign.status === 'TIMEOUT') && (
+
+ )}
+ {campaign.status === 'OPENING' && (
+
+ )}
+ >
+ )}
+ />
+ )
+ }
+
+ const renderStatusToolTips = (campaign) => {
+ return (
+
+ {campaign.status === 'DISCONNECTED' && (
+
+
+
+ )}
+ {campaign.status === 'OPENING' && (
+
+ )}
+ {campaign.status === 'qrcode' && (
+
+
+
+ )}
+ {campaign.status === 'CONNECTED' && (
+
+
+
+ )}
+ {(campaign.status === 'TIMEOUT' || campaign.status === 'PAIRING') && (
+
+
+
+ )}
+
+ {/* {campaign.status === "RESTORING" && (
+
+
+
+ )} */}
+
+ )
+ }
+
+ useEffect(() => {
+ const delayDebounceFn = setTimeout(() => {
+ const fetchQueries = async () => {
+ try {
+ await api.post(`/restartwhatsappsession/0`, {
+ params: { status: 'status' },
+ })
+
+ setDisabled(false)
+
+ setClicks((buttons) =>
+ buttons.map((e) => {
+ return { id: e.id, disabled: false }
+ })
+ )
+ } catch (err) {
+ console.log(err)
+ }
+ }
+
+ fetchQueries()
+ }, 500)
+ return () => clearTimeout(delayDebounceFn)
+ }, [])
+
+ useEffect(() => {
+ const socket = openSocket(process.env.REACT_APP_BACKEND_URL)
+
+ socket.on('diskSpaceMonit', (data) => {
+ if (data.action === 'update') {
+ 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 () => {
+ socket.disconnect()
+ }
+ }, [])
+
+ return (
+ (
+
+
+ {confirmModalInfo.message}
+
+
+
+
+
+
+
+ Campanhas
+
+
+ (
+
+ )}
+ />
+
+
+
+
+ <>
+
+
+
+
+ Nome
+
+
+
+ Status
+
+
+ (
+
+ {i18n.t('connections.table.session')}
+
+ )}
+ />
+
+
+ Whatsapp sender
+
+
+
+
+ Mensagem
+
+
+
+ {i18n.t('connections.table.actions')}
+
+
+
+
+ {loading ? (
+
+ ) : (
+ <>
+ {campaigns?.length > 0 &&
+ campaigns.map((campaign) => (
+
+
+ {campaign.name}
+
+
+
+ Status
+
+
+ (
+
+ {renderActionButtons(campaign)}
+
+ )}
+ />
+
+
+ {campaign.whatsapp_sender}
+
+
+
+ {campaign.message}
+
+
+
+ (
+
+ {(settings &&
+ settings.length > 0 &&
+ getSettingValue('editURA') &&
+ getSettingValue('editURA') ===
+ 'enabled') |
+ (user.profile === 'master') ? (
+
+ handleEditCampaign(campaign)
+ }
+ >
+
+
+ ) : (
+
+ )}
+
+ )}
+ />
+
+ (
+ {
+ handleOpenConfirmationModal(
+ 'delete',
+ campaign.id
+ )
+ }}
+ >
+
+
+ )}
+ />
+
+
+
+
+ ))}
+ >
+ )}
+
+
+ >
+
+
+ )}
+ />
+ )
+}
+
+export default Campaign
diff --git a/frontend/src/pages/Contacts/index.js b/frontend/src/pages/Contacts/index.js
index d67298b..ab8a653 100644
--- a/frontend/src/pages/Contacts/index.js
+++ b/frontend/src/pages/Contacts/index.js
@@ -1,43 +1,43 @@
-import React, { useState, useEffect, useReducer, useContext } from "react";
-import openSocket from "socket.io-client";
-import { toast } from "react-toastify";
-import { useHistory } from "react-router-dom";
+import React, { useState, useEffect, useReducer, useContext } from "react"
+import openSocket from "socket.io-client"
+import { toast } from "react-toastify"
+import { useHistory } from "react-router-dom"
-import { makeStyles } from "@material-ui/core/styles";
-import Table from "@material-ui/core/Table";
-import TableBody from "@material-ui/core/TableBody";
-import TableCell from "@material-ui/core/TableCell";
-import TableHead from "@material-ui/core/TableHead";
-import TableRow from "@material-ui/core/TableRow";
-import Paper from "@material-ui/core/Paper";
-import Button from "@material-ui/core/Button";
-import Avatar from "@material-ui/core/Avatar";
-import WhatsAppIcon from "@material-ui/icons/WhatsApp";
-import SearchIcon from "@material-ui/icons/Search";
-import TextField from "@material-ui/core/TextField";
-import InputAdornment from "@material-ui/core/InputAdornment";
+import { makeStyles } from "@material-ui/core/styles"
+import Table from "@material-ui/core/Table"
+import TableBody from "@material-ui/core/TableBody"
+import TableCell from "@material-ui/core/TableCell"
+import TableHead from "@material-ui/core/TableHead"
+import TableRow from "@material-ui/core/TableRow"
+import Paper from "@material-ui/core/Paper"
+import Button from "@material-ui/core/Button"
+import Avatar from "@material-ui/core/Avatar"
+import WhatsAppIcon from "@material-ui/icons/WhatsApp"
+import SearchIcon from "@material-ui/icons/Search"
+import TextField from "@material-ui/core/TextField"
+import InputAdornment from "@material-ui/core/InputAdornment"
-import IconButton from "@material-ui/core/IconButton";
-import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
-import EditIcon from "@material-ui/icons/Edit";
+import IconButton from "@material-ui/core/IconButton"
+import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline"
+import EditIcon from "@material-ui/icons/Edit"
-import api from "../../services/api";
-import TableRowSkeleton from "../../components/TableRowSkeleton";
-import ContactModal from "../../components/ContactModal";
-import ConfirmationModal from "../../components/ConfirmationModal/";
+import api from "../../services/api"
+import TableRowSkeleton from "../../components/TableRowSkeleton"
+import ContactModal from "../../components/ContactModal"
+import ConfirmationModal from "../../components/ConfirmationModal/"
-import { i18n } from "../../translate/i18n";
-import MainHeader from "../../components/MainHeader";
-import Title from "../../components/Title";
-import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper";
-import MainContainer from "../../components/MainContainer";
-import toastError from "../../errors/toastError";
-import { AuthContext } from "../../context/Auth/AuthContext";
-import { Can } from "../../components/Can";
+import { i18n } from "../../translate/i18n"
+import MainHeader from "../../components/MainHeader"
+import Title from "../../components/Title"
+import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper"
+import MainContainer from "../../components/MainContainer"
+import toastError from "../../errors/toastError"
+import { AuthContext } from "../../context/Auth/AuthContext"
+import { Can } from "../../components/Can"
-import apiBroker from "../../services/apiBroker";
+import apiBroker from "../../services/apiBroker"
import fileDownload from 'js-file-download'
-import ContactCreateTicketModal from "../../components/ContactCreateTicketModal";
+import ContactCreateTicketModal from "../../components/ContactCreateTicketModal"
@@ -46,50 +46,50 @@ const reducer = (state, action) => {
if (action.type === "LOAD_CONTACTS") {
- const contacts = action.payload;
- const newContacts = [];
+ const contacts = action.payload
+ const newContacts = []
contacts.forEach((contact) => {
- const contactIndex = state.findIndex((c) => +c.id === +contact.id);
+ const contactIndex = state.findIndex((c) => +c.id === +contact.id)
if (contactIndex !== -1) {
- state[contactIndex] = contact;
+ state[contactIndex] = contact
} else {
- newContacts.push(contact);
+ newContacts.push(contact)
}
- });
+ })
- return [...state, ...newContacts];
+ return [...state, ...newContacts]
}
if (action.type === "UPDATE_CONTACTS") {
- const contact = action.payload;
- const contactIndex = state.findIndex((c) => +c.id === +contact.id);
+ const contact = action.payload
+ const contactIndex = state.findIndex((c) => +c.id === +contact.id)
if (contactIndex !== -1) {
- state[contactIndex] = contact;
- return [...state];
+ state[contactIndex] = contact
+ return [...state]
} else {
- return [contact, ...state];
+ return [contact, ...state]
}
}
if (action.type === "DELETE_CONTACT") {
- const contactId = action.payload;
+ const contactId = action.payload
- const contactIndex = state.findIndex((c) => +c.id === +contactId);
+ const contactIndex = state.findIndex((c) => +c.id === +contactId)
if (contactIndex !== -1) {
- state.splice(contactIndex, 1);
+ state.splice(contactIndex, 1)
}
- return [...state];
+ return [...state]
}
if (action.type === "RESET") {
- return [];
+ return []
}
-};
+}
const useStyles = makeStyles((theme) => ({
mainPaper: {
@@ -98,24 +98,24 @@ const useStyles = makeStyles((theme) => ({
overflowY: "scroll",
...theme.scrollbarStyles,
},
-}));
+}))
const Contacts = () => {
- const classes = useStyles();
- const history = useHistory();
+ const classes = useStyles()
+ const history = useHistory()
- const { user } = useContext(AuthContext);
+ const { user } = useContext(AuthContext)
- const [loading, setLoading] = useState(false);
- const [pageNumber, setPageNumber] = useState(1);
- const [searchParam, setSearchParam] = useState("");
- const [contacts, dispatch] = useReducer(reducer, []);
- const [selectedContactId, setSelectedContactId] = useState(null);
- const [contactModalOpen, setContactModalOpen] = useState(false);
+ const [loading, setLoading] = useState(false)
+ const [pageNumber, setPageNumber] = useState(1)
+ const [searchParam, setSearchParam] = useState("")
+ const [contacts, dispatch] = useReducer(reducer, [])
+ const [selectedContactId, setSelectedContactId] = useState(null)
+ const [contactModalOpen, setContactModalOpen] = useState(false)
const [isCreateTicketModalOpen, setIsCreateTicketModalOpen] = useState(false)
- const [deletingContact, setDeletingContact] = useState(null);
- const [confirmOpen, setConfirmOpen] = useState(false);
- const [hasMore, setHasMore] = useState(false);
+ const [deletingContact, setDeletingContact] = useState(null)
+ const [confirmOpen, setConfirmOpen] = useState(false)
+ const [hasMore, setHasMore] = useState(false)
const [onQueueStatus, setOnQueueProcessStatus] = useState(undefined)
@@ -132,20 +132,20 @@ const Contacts = () => {
return
}
- const formData = new FormData();
- formData.append("adminId", user.id);
- formData.append("baseURL", process.env.REACT_APP_BACKEND_URL_PRIVATE,);
- formData.append("frontURL", process.env.REACT_APP_FRONTEND_URL);
- formData.append("identifier", 'contacts_insert_csv');
- formData.append("file", event.target.files[0]);
+ const formData = new FormData()
+ formData.append("adminId", user.id)
+ formData.append("baseURL", process.env.REACT_APP_BACKEND_URL_PRIVATE,)
+ formData.append("frontURL", process.env.REACT_APP_FRONTEND_URL)
+ formData.append("identifier", 'contacts_insert_csv')
+ formData.append("file", event.target.files[0])
const config = {
headers: {
'content-type': 'multipart/form-data',
},
- };
+ }
- const bulk_contact_insert = await apiBroker.post("/contacts/bulk/insert", formData, config);
+ const bulk_contact_insert = await apiBroker.post("/contacts/bulk/insert", formData, config)
console.log(bulk_contact_insert.data)
@@ -156,7 +156,7 @@ const Contacts = () => {
// history.go(0);
} catch (err) {
- toastError(err);
+ toastError(err)
}
}
@@ -177,7 +177,7 @@ const Contacts = () => {
baseURL: process.env.REACT_APP_BACKEND_URL_PRIVATE,
identifier: 'contacts_insert_csv'
}
- });
+ })
if (insertOnQueue && insertOnQueue.data) {
@@ -193,23 +193,23 @@ const Contacts = () => {
} catch (err) {
- console.log(err);
+ console.log(err)
}
- };
+ }
- fetchReportOnQueue();
+ fetchReportOnQueue()
- }, 500);
- return () => clearTimeout(delayDebounceFn);
+ }, 500)
+ return () => clearTimeout(delayDebounceFn)
}, [user])
useEffect(() => {
- dispatch({ type: "RESET" });
- setPageNumber(1);
- }, [searchParam]);
+ dispatch({ type: "RESET" })
+ setPageNumber(1)
+ }, [searchParam])
useEffect(() => {
@@ -220,7 +220,7 @@ const Contacts = () => {
- setLoading(true);
+ setLoading(true)
const delayDebounceFn = setTimeout(() => {
const fetchContacts = async () => {
@@ -230,25 +230,25 @@ const Contacts = () => {
return
}
- const { data } = await api.get("/contacts/", { params: { searchParam, pageNumber }, });
+ const { data } = await api.get("/contacts/", { params: { searchParam, pageNumber }, })
- dispatch({ type: "LOAD_CONTACTS", payload: data.contacts });
- setHasMore(data.hasMore);
- setLoading(false);
+ dispatch({ type: "LOAD_CONTACTS", payload: data.contacts })
+ setHasMore(data.hasMore)
+ setLoading(false)
} catch (err) {
- toastError(err);
+ toastError(err)
}
- };
- fetchContacts();
- }, 500);
- return () => clearTimeout(delayDebounceFn);
- }, [searchParam, pageNumber]);
+ }
+ fetchContacts()
+ }, 500)
+ return () => clearTimeout(delayDebounceFn)
+ }, [searchParam, pageNumber])
useEffect(() => {
- const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
+ const socket = openSocket(process.env.REACT_APP_BACKEND_URL)
socket.on("contactsBulkInsertOnQueueStatus", (data) => {
if (data.action === 'update') {
@@ -261,7 +261,7 @@ const Contacts = () => {
setOnQueueProcessStatus(data.insertOnQueue.queueStatus)
if (data.insertOnQueue.queueStatus === "success") {
- history.go(0);
+ history.go(0)
}
}
@@ -270,18 +270,18 @@ const Contacts = () => {
socket.on("contact", (data) => {
if (data.action === "update" || data.action === "create") {
- dispatch({ type: "UPDATE_CONTACTS", payload: data.contact });
+ dispatch({ type: "UPDATE_CONTACTS", payload: data.contact })
}
if (data.action === "delete") {
- dispatch({ type: "DELETE_CONTACT", payload: +data.contactId });
+ dispatch({ type: "DELETE_CONTACT", payload: +data.contactId })
}
- });
+ })
return () => {
- socket.disconnect();
- };
- }, [user, history]);
+ socket.disconnect()
+ }
+ }, [user, history])
const removeExtraSpace = (str) => {
@@ -291,18 +291,18 @@ const Contacts = () => {
}
const handleSearch = (event) => {
- setSearchParam(removeExtraSpace(event.target.value.toLowerCase()));
- };
+ setSearchParam(removeExtraSpace(event.target.value.toLowerCase()))
+ }
const handleOpenContactModal = () => {
- setSelectedContactId(null);
- setContactModalOpen(true);
- };
+ setSelectedContactId(null)
+ setContactModalOpen(true)
+ }
const handleCloseContactModal = () => {
- setSelectedContactId(null);
- setContactModalOpen(false);
- };
+ setSelectedContactId(null)
+ setContactModalOpen(false)
+ }
const handleOpenCreateTicketModal = (contactId) => {
setSelectedContactId(contactId)
@@ -330,46 +330,46 @@ const Contacts = () => {
// };
const hadleEditContact = (contactId) => {
- setSelectedContactId(contactId);
- setContactModalOpen(true);
- };
+ setSelectedContactId(contactId)
+ setContactModalOpen(true)
+ }
const handleDeleteContact = async (contactId) => {
try {
- await api.delete(`/contacts/${contactId}`);
- toast.success(i18n.t("contacts.toasts.deleted"));
+ await api.delete(`/contacts/${contactId}`)
+ toast.success(i18n.t("contacts.toasts.deleted"))
} catch (err) {
- toastError(err);
+ toastError(err)
}
- setDeletingContact(null);
- setSearchParam("");
- setPageNumber(1);
- };
+ setDeletingContact(null)
+ setSearchParam("")
+ setPageNumber(1)
+ }
const handleimportContact = async () => {
try {
- await api.post("/contacts/import");
- history.go(0);
+ await api.post("/contacts/import")
+ history.go(0)
} catch (err) {
- toastError(err);
+ toastError(err)
}
- };
+ }
const loadMore = () => {
- setPageNumber((prevState) => prevState + 1);
- };
+ setPageNumber((prevState) => prevState + 1)
+ }
const handleScroll = (e) => {
- if (!hasMore || loading) return;
- const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
+ if (!hasMore || loading) return
+ const { scrollTop, scrollHeight, clientHeight } = e.currentTarget
if (scrollHeight - (scrollTop + 100) < clientHeight) {
- loadMore();
+ loadMore()
}
- };
+ }
const handleDownload = async () => {
@@ -378,16 +378,16 @@ const Contacts = () => {
try {
- let res = await apiBroker.get(`/contacts/download/${zipfile}`, { responseType: 'blob' });
+ let res = await apiBroker.get(`/contacts/download/${zipfile}`, { responseType: 'blob' })
if (res) {
- fileDownload(res.data, `${zipfile}`);
+ fileDownload(res.data, `${zipfile}`)
setOnQueueProcessStatus('empty')
}
} catch (err) {
- console.log(err);
+ console.log(err)
}
@@ -406,7 +406,7 @@ const Contacts = () => {
baseURL: process.env.REACT_APP_BACKEND_URL_PRIVATE,
identifier: 'contacts_insert_csv'
}
- });
+ })
if (res) {
setOnQueueProcessStatus('empty')
@@ -414,7 +414,7 @@ const Contacts = () => {
} catch (err) {
- console.log(err);
+ console.log(err)
}
@@ -452,13 +452,13 @@ const Contacts = () => {
>
{"CSV ALL"}
*/}
- >);
+ >)
case 'pending' || 'processing':
return (
<>
PROCESSING...
- >);
+ >)
case 'success':
return (
@@ -472,7 +472,7 @@ const Contacts = () => {
>
{'DOWNLOAD'}
- >);
+ >)
case 'error':
return (
<>
@@ -485,16 +485,16 @@ const Contacts = () => {
>
{'ERROR'}
- >);
+ >)
case 'downloading':
return (
<>
DOWNLOADING...
- >);
+ >)
default:
- return (<>WAITING...>);
+ return (<>WAITING...>)
}
}
@@ -642,8 +642,8 @@ const Contacts = () => {
{
- setConfirmOpen(true);
- setDeletingContact(contact);
+ setConfirmOpen(true)
+ setDeletingContact(contact)
}}
>
@@ -659,7 +659,7 @@ const Contacts = () => {
- );
-};
+ )
+}
-export default Contacts;
\ No newline at end of file
+export default Contacts
\ No newline at end of file
diff --git a/frontend/src/routes/index.js b/frontend/src/routes/index.js
index 1043761..ba35368 100644
--- a/frontend/src/routes/index.js
+++ b/frontend/src/routes/index.js
@@ -1,27 +1,26 @@
-import React from "react";
-import { BrowserRouter, Switch } from "react-router-dom";
-import { ToastContainer } from "react-toastify";
+import React from 'react'
+import { BrowserRouter, Switch } from 'react-router-dom'
+import { ToastContainer } from 'react-toastify'
-import LoggedInLayout from "../layout";
-import Dashboard from "../pages/Dashboard/";
-
-import Report from "../pages/Report/";
-import SchedulesReminder from "../pages/SchedulesReminder/";
-
-import Tickets from "../pages/Tickets/";
-import Signup from "../pages/Signup/";
-import Login from "../pages/Login/";
-import Connections from "../pages/Connections/";
-import Settings from "../pages/Settings/";
-import Users from "../pages/Users";
-import Contacts from "../pages/Contacts/";
-import QuickAnswers from "../pages/QuickAnswers/";
-import Queues from "../pages/Queues/";
-import { AuthProvider } from "../context/Auth/AuthContext";
-import { WhatsAppsProvider } from "../context/WhatsApp/WhatsAppsContext";
-import Route from "./Route";
+import LoggedInLayout from '../layout'
+import Dashboard from '../pages/Dashboard/'
+import Report from '../pages/Report/'
+import SchedulesReminder from '../pages/SchedulesReminder/'
+import Tickets from '../pages/Tickets/'
+import Signup from '../pages/Signup/'
+import Login from '../pages/Login/'
+import Connections from '../pages/Connections/'
+import Campaign from '../pages/Campaign'
+import Settings from '../pages/Settings/'
+import Users from '../pages/Users'
+import Contacts from '../pages/Contacts/'
+import QuickAnswers from '../pages/QuickAnswers/'
+import Queues from '../pages/Queues/'
+import { AuthProvider } from '../context/Auth/AuthContext'
+import { WhatsAppsProvider } from '../context/WhatsApp/WhatsAppsContext'
+import Route from './Route'
const Routes = () => {
return (
@@ -33,27 +32,48 @@ const Routes = () => {
-
+
-
+
-
+
-
+
+
- );
-};
+ )
+}
-export default Routes;
+export default Routes