From ff0d2c3630dc7c562f947e1fc658fe7d7a40b128 Mon Sep 17 00:00:00 2001 From: adriano Date: Fri, 4 Aug 2023 16:16:19 -0300 Subject: [PATCH] updante do modulo campanha em andamento --- .../src/components/CampaignModal/index.js | 346 +++++++++ frontend/src/layout/MainListItems.js | 138 ++-- frontend/src/pages/Campaign/index.js | 696 ++++++++++++++++++ frontend/src/pages/Contacts/index.js | 292 ++++---- frontend/src/routes/index.js | 76 +- 5 files changed, 1316 insertions(+), 232 deletions(-) create mode 100644 frontend/src/components/CampaignModal/index.js create mode 100644 frontend/src/pages/Campaign/index.js 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 ( +
+ + + {campaignId ? 'Editar campanha' : 'Adicionar campanha'} + + { + setTimeout(() => { + handleSaveCampaign(values) + actions.setSubmitting(false) + }, 400) + }} + > + {({ values, touched, errors, isSubmitting }) => ( + +
+ + ( + <> +
+
+ + + +
+ +
+ +
+
+ + )} + /> + +
+ +
+ +
+ + + + +

{file?.name || campaign?.csv_original_file_name}

+
+
+ + + + +
+ )} +
+
+
+ ) +} + +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