diff --git a/backend/src/controllers/SchedulingNotifyController.ts b/backend/src/controllers/SchedulingNotifyController.ts index 4185686..7765d00 100644 --- a/backend/src/controllers/SchedulingNotifyController.ts +++ b/backend/src/controllers/SchedulingNotifyController.ts @@ -1,8 +1,35 @@ import { Request, Response } from "express"; +import AppError from "../errors/AppError"; import DeleteSchedulingNotifyService from "../services/SchedulingNotifyServices/DeleteSchedulingNotifyService"; +import ListSchedulingNotifyContactService from "../services/SchedulingNotifyServices/ListSchedulingNotifyContactService"; +// const test = await ListSchedulingNotifyContactService('5517988310949','2022-03-18','2022-03-19'); +// const test = await ListSchedulingNotifyContactService('','2022-03-18','2022-03-19'); +// const test = await ListSchedulingNotifyContactService('5517988310949'); +// console.log('$$$$$$$$$$$$$$$$$$$$$$$$$$ test:\n', test) + + +type IndexQuery = { + contactNumber: string; + startDate: string; + endDate: string; +}; + + +export const reportScheduleNotifyByDateStartDateEnd = async (req: Request, res: Response): Promise => { + + const { contactNumber, startDate, endDate } = req.query as IndexQuery + + const data_query = await ListSchedulingNotifyContactService(contactNumber, startDate, endDate); + + console.group('DATA QUERY SCHEDULE:\n',data_query) + + return res.status(200).json(data_query); + +}; + export const remove = async ( req: Request, res: Response ): Promise => { console.log('EEEEEEEEEEEEEEEEEEEEEEEEEEE') @@ -13,3 +40,4 @@ export const remove = async ( req: Request, res: Response ): Promise = return res.status(200).send(); }; + diff --git a/backend/src/controllers/TicketController.ts b/backend/src/controllers/TicketController.ts index d4274d1..ff56802 100644 --- a/backend/src/controllers/TicketController.ts +++ b/backend/src/controllers/TicketController.ts @@ -94,6 +94,7 @@ export const show = async (req: Request, res: Response): Promise => { const contact = await ShowTicketService(ticketId); const { schedules, count, hasMore } = await ListScheduleService({ searchParam: "", pageNumber: "1" }); + ////////////////// const schedulesContact = await ListSchedulingNotifyContactService(contact.contact.number); diff --git a/backend/src/routes/SchedulingNotifyRoutes.ts b/backend/src/routes/SchedulingNotifyRoutes.ts index 975b5a1..affa282 100644 --- a/backend/src/routes/SchedulingNotifyRoutes.ts +++ b/backend/src/routes/SchedulingNotifyRoutes.ts @@ -7,4 +7,6 @@ const schedulingNotifiyRoutes = Router(); schedulingNotifiyRoutes.delete("/schedule/:scheduleId", isAuth, SchedulingNotifyController.remove); +schedulingNotifiyRoutes.get("/schedules", isAuth, SchedulingNotifyController.reportScheduleNotifyByDateStartDateEnd); + export default schedulingNotifiyRoutes; diff --git a/backend/src/services/SchedulingNotifyServices/ListSchedulingNotifyContactService.ts b/backend/src/services/SchedulingNotifyServices/ListSchedulingNotifyContactService.ts index 2a20ee0..72ba2fe 100644 --- a/backend/src/services/SchedulingNotifyServices/ListSchedulingNotifyContactService.ts +++ b/backend/src/services/SchedulingNotifyServices/ListSchedulingNotifyContactService.ts @@ -4,26 +4,127 @@ import SchedulingNotify from "../../models/SchedulingNotify"; import { Op, where, Sequelize } from "sequelize"; import AppError from "../../errors/AppError"; -const ListSchedulingNotifyContactService = async (contactNumber: string): Promise => { +const ListSchedulingNotifyContactService = async (contactNumber: string = '', startDate: string='', endDate: string=''): Promise => { + + let where_clause = {} + let where_clause_notify = {} + + let nameNumber = { + + [Op.or]: [ + { + name: Sequelize.where( + Sequelize.fn("LOWER", Sequelize.col("name")), + "LIKE", + `%${contactNumber.toLowerCase().trim()}%` + ) + }, + { number: { [Op.like]: `%${contactNumber.toLowerCase().trim()}%` } } + ] + + } + + + + + if (contactNumber.trim().length>0 && startDate.trim().length>0 && endDate.trim().length>0){ + + where_clause = nameNumber + + where_clause_notify = { + schedulingTime: { + [Op.gte]: startDate+' 00:00:00.000000', + [Op.lte]: endDate +' 23:59:59.999999' + }, + } + + } + else if (contactNumber.trim().length==0 && startDate.trim().length>0 && endDate.trim().length>0){ + + where_clause = {} + + where_clause_notify = { + schedulingTime: { + [Op.gte]: startDate+' 00:00:00.000000', + [Op.lte]: endDate +' 23:59:59.999999' + }, + } + + } + else if (contactNumber.trim().length==0 && startDate.trim().length>0 && endDate.trim().length==0){ + + where_clause = {} + + where_clause_notify = { + schedulingTime: { + [Op.gte]: startDate+' 00:00:00.000000', + [Op.lte]: startDate +' 23:59:59.999999' + }, + } + + } + else if (contactNumber.trim().length==0 && startDate.trim().length==0 && endDate.trim().length>0){ + + where_clause = {} + + where_clause_notify = { + schedulingTime: { + [Op.gte]: endDate+' 00:00:00.000000', + [Op.lte]: endDate +' 23:59:59.999999' + }, + } + + } + else if (contactNumber.trim().length>0 && startDate.trim().length>0 && endDate.trim().length==0){ + + where_clause = nameNumber + + where_clause_notify = { + schedulingTime: { + [Op.gte]: startDate+' 00:00:00.000000', + [Op.lte]: startDate +' 23:59:59.999999' + }, + } + + } + else if (contactNumber.trim().length>0 && startDate.trim().length==0 && endDate.trim().length>0){ + + where_clause = nameNumber + + where_clause_notify = { + schedulingTime: { + [Op.gte]: endDate+' 00:00:00.000000', + [Op.lte]: endDate +' 23:59:59.999999' + }, + } + + } + else if(contactNumber.trim().length>0){ + + where_clause = nameNumber + + } + const ticket = await SchedulingNotify.findAll({ + + raw: true, + where: where_clause_notify, attributes:['id', [Sequelize.fn("DATE_FORMAT",Sequelize.col("schedulingDate"),"%d/%m/%Y %H:%i:%s"),"schedulingDate"], [Sequelize.fn("DATE_FORMAT",Sequelize.col("schedulingTime"),"%d/%m/%Y %H:%i:%s"),"schedulingTime"], 'message'], include: [ - { - model: Ticket, + { + model: Ticket, required:true, attributes: [], include: [ { - model: Contact, - where:{ - number: contactNumber, - }, - attributes: ['name', 'number'] + model: Contact, + where: where_clause, + attributes: ['name', 'number', 'profilePicUrl'] }, ] }, diff --git a/frontend/src/components/ChatEnd/ModalChatEnd/index.js b/frontend/src/components/ChatEnd/ModalChatEnd/index.js index 36e71f6..0bb9c34 100644 --- a/frontend/src/components/ChatEnd/ModalChatEnd/index.js +++ b/frontend/src/components/ChatEnd/ModalChatEnd/index.js @@ -28,7 +28,8 @@ import { } from "@material-ui/core"; import { DeleteOutline } from "@material-ui/icons"; -import { toast } from "react-toastify"; import api from "../../../services/api"; +import { toast } from "react-toastify"; + import api from "../../../services/api"; import toastError from "../../../errors/toastError"; import ConfirmationModal from "../../ConfirmationModal"; @@ -337,7 +338,7 @@ const handleChange = (event) => { - + @@ -351,7 +352,7 @@ const handleChange = (event) => { aria-label="minimum height" minRows={3} value={textArea1} - placeholder={'Mensagem para lembrar cliente'} + placeholder={'Mensagem de envio para cliente'} onChange={ handleChange} style={{ width: '100%' }} /> @@ -375,7 +376,7 @@ const handleChange = (event) => { > Deseja realmente deletar esse Lembrete? - Lembrete + Lembretes diff --git a/frontend/src/components/Report/DatePicker/index.js b/frontend/src/components/Report/DatePicker/index.js index 7f0597c..db4ae5c 100644 --- a/frontend/src/components/Report/DatePicker/index.js +++ b/frontend/src/components/Report/DatePicker/index.js @@ -22,13 +22,28 @@ function formatDateDatePicker(data){ } function ResponsiveDatePickers(props) { - const [selectedDate, handleDateChange] = useState(new Date()); + const [selectedDate, handleDateChange] = useState(props.startEmpty? null : new Date()); + - // props.func(formatDateDatePicker(selectedDate)); + ////////////////////////// + useEffect(() => { + if (props.reset) { + handleDateChange(null) + props.setReset(false) + } + }, [props.reset, props.setReset, props]) + ///////////////////////// useEffect(()=>{ - - props.func(formatDateDatePicker(selectedDate)); + + + if( !selectedDate ){ + props.func(''); + } + else{ + props.func(formatDateDatePicker(selectedDate)); + } + }, [selectedDate, props]) @@ -37,15 +52,17 @@ function ResponsiveDatePickers(props) { handleDateChange(date)} + /> diff --git a/frontend/src/components/Report/DatePicker2/index.js b/frontend/src/components/Report/DatePicker2/index.js new file mode 100644 index 0000000..9002b42 --- /dev/null +++ b/frontend/src/components/Report/DatePicker2/index.js @@ -0,0 +1,97 @@ + + +import React, { Fragment, useState, useEffect } from "react"; + + +import DateFnsUtils from '@date-io/date-fns'; // choose your lib +import { + KeyboardDatePicker, + MuiPickersUtilsProvider, +} from '@material-ui/pickers'; + + + + +import ptBrLocale from "date-fns/locale/pt-BR"; + + +function formatDateDatePicker(data){ + return String(new Date(data).getFullYear())+'-'+ + String(new Date(data).getMonth() + 1).padStart(2,'0')+'-'+ + String(new Date(data).getDate()).padStart(2,'0') +} + +function ResponsiveDatePickers(props) { + const [selectedDate, handleDateChange] = useState(null); + + // props.func(formatDateDatePicker(selectedDate)); + + useEffect(()=>{ + + if( !selectedDate ){ + props.func(''); + } + else{ + props.func(formatDateDatePicker(selectedDate)); + } + + + + }, [selectedDate, props]) + + return ( + + + + handleDateChange(date)} + /> + + + ); +} + +export default ResponsiveDatePickers; + + + + +/*import * as React from 'react'; +import TextField from '@mui/material/TextField'; +import AdapterDateFns from '@mui/lab/AdapterDateFns'; +import LocalizationProvider from '@mui/lab/LocalizationProvider'; +import DesktopDatePicker from '@mui/lab/DesktopDatePicker'; +import Stack from '@mui/material/Stack'; + +const ResponsiveDatePickers = (props) => { + + const [value, setValue] = React.useState(new Date()); + + props.func(value); + + return ( + + + {setValue(newValue)}} + renderInput={(params) => } + /> + + + ); +} + +export default ResponsiveDatePickers*/ \ No newline at end of file diff --git a/frontend/src/components/Report/MTable/index.js b/frontend/src/components/Report/MTable/index.js index a066ab4..fabc0a0 100644 --- a/frontend/src/components/Report/MTable/index.js +++ b/frontend/src/components/Report/MTable/index.js @@ -1,77 +1,81 @@ - -import { useState, useEffect} from 'react'; -import MaterialTable from 'material-table'; -import Modal from '../Modal' + +import { useState, useEffect } from 'react'; +import MaterialTable from 'material-table'; +import Modal from '../Modal' import { render } from '@testing-library/react'; -import React from 'react'; +import React from 'react'; -const MTable = (props) => { - - const [selectedRow, setSelectedRow] = useState(null); +const MTable = (props) => { + + const [selectedRow, setSelectedRow] = useState(null); //const dataLoad = props.data.map((dt) => { return { ...dt }}); - const dataLoad = props.data.map(({user, ...others})=>({...others, 'user': user ? user : {name: 'Aguardando atendente', email:''}})); + const dataLoad = props.data.map(({ user, ...others }) => ({ ...others, 'user': user ? user : { name: 'Aguardando atendente', email: '' } })); + + const columnsLoad = props.columns.map((column) => { return { ...column } }); - const columnsLoad = props.columns.map((column) => { return { ...column }}); - useEffect(() => { - - console.log(`You have clicked the button ${selectedRow} times`) - - },[selectedRow]); - return ( - - { + }, [selectedRow]); - console.log(selectedRow.tableData.id); - console.log(selectedRow); - console.log(selectedRow.messages); - setSelectedRow(selectedRow.tableData.id) + return ( - if(props.hasChild) { - render() - } - - console.log('props.hasChild: ', props.hasChild) + ({ - fontSize: 12, - backgroundColor: selectedRow === rowData.tableData.id ? '#ec5114' : '#FFF' - }) - }} - /> - ); + maxWidth={true} + + onRowClick={(evt, selectedRow) => { - }; + if(props.removeClickRow){ + return + } + console.log(selectedRow.tableData.id); + console.log(selectedRow); + console.log(selectedRow.messages); + setSelectedRow(selectedRow.tableData.id) - export default React.memo(MTable) \ No newline at end of file + if (props.hasChild) { + render() + } + + console.log('props.hasChild: ', props.hasChild) + + //evt.stopPropagation() + } + } + + options={{ + search: true, + selection: false, + paging: false, + padding: 'dense', + sorting: true ? props.hasChild : false, + //loadingType: 'linear', + searchFieldStyle: { + width: 300, + }, + + rowStyle: rowData => ({ + fontSize: 12, + backgroundColor: selectedRow === rowData.tableData.id ? '#ec5114' : '#FFF' + }) + }} + /> + ); + +}; + + +export default React.memo(MTable) \ No newline at end of file diff --git a/frontend/src/layout/MainListItems.js b/frontend/src/layout/MainListItems.js index 93613fb..c524b76 100644 --- a/frontend/src/layout/MainListItems.js +++ b/frontend/src/layout/MainListItems.js @@ -9,7 +9,9 @@ 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 ReportOutlinedIcon from "@material-ui/icons/ReportOutlined"; +import SendOutlined from '@material-ui/icons/SendOutlined'; + //import ReportOutlined from "@bit/mui-org.material-ui-icons.report-outlined"; @@ -89,6 +91,11 @@ const MainListItems = (props) => { to="/contacts" primary={i18n.t("mainDrawer.listItems.contacts")} icon={} + /> + } /> { return {'value': obj.id, 'label': obj.name} })}/> - - + + {/* */} @@ -282,7 +282,8 @@ const textFieldSelectUser = (data) => { diff --git a/frontend/src/pages/SchedulesReminder/index.js b/frontend/src/pages/SchedulesReminder/index.js new file mode 100644 index 0000000..b156842 --- /dev/null +++ b/frontend/src/pages/SchedulesReminder/index.js @@ -0,0 +1,427 @@ +import React, { useState, useEffect, useReducer, useContext} from "react"; +import MainContainer from "../../components/MainContainer"; +import api from "../../services/api"; +import SelectField from "../../components/Report/SelectField"; +//import { data } from '../../components/Report/MTable/data'; +import DatePicker1 from '../../components/Report/DatePicker' +import DatePicker2 from '../../components/Report/DatePicker' +//import { Button } from "@material-ui/core"; +import MTable from "../../components/Report/MTable"; +import PropTypes from 'prop-types'; +import Box from '@mui/material/Box'; +import { AuthContext } from "../../context/Auth/AuthContext"; +import { Can } from "../../components/Can"; + +import SearchIcon from "@material-ui/icons/Search"; +import TextField from "@material-ui/core/TextField"; +import InputAdornment from "@material-ui/core/InputAdornment"; +import Button from "@material-ui/core/Button"; + +import MaterialTable from 'material-table'; + +import Delete from '@material-ui/icons/Delete'; +import Edit from '@material-ui/icons/Edit'; +import Save from '@material-ui/icons/Save'; + +import { + IconButton, + Paper, + Table, + TableBody, + TableCell, + TableHead, + TableRow, +} from "@material-ui/core"; + +import { DeleteOutline } from "@material-ui/icons"; +import { toast } from "react-toastify"; +import toastError from "../../errors/toastError"; +import ConfirmationModal from "../../components/ConfirmationModal"; + + + +const reducerQ = (state, action) =>{ + + if(action.type === 'LOAD_QUERY'){ + + console.log('----------------action.payload: ', action.payload) + const queries = action.payload + const newQueries = [] + + queries.forEach((query) => { + + const queryIndex = state.findIndex((q) => q.id === query.id) + + if(queryIndex !== -1){ + state[queryIndex] = query + } + else{ + newQueries.push(query) + } + + }) + + return [...state, ...newQueries] + } + + if (action.type === "DELETE_SCHEDULE") { + const scheduleId = action.payload; + + const scheduleIndex = state.findIndex((q) => q.id === scheduleId); + if (scheduleIndex !== -1) { + state.splice(scheduleIndex, 1); + } + return [...state]; + } + + + if (action.type === "RESET") { + return []; + } + +} + + + +const reducer = (state, action) => { + + if (action.type === "LOAD_USERS") { + const users = action.payload; + const newUsers = []; + + users.forEach((user) => { + const userIndex = state.findIndex((u) => u.id === user.id); + if (userIndex !== -1) { + state[userIndex] = user; + } else { + newUsers.push(user); + } + }); + + return [...state, ...newUsers]; + } + + if (action.type === "RESET") { + return []; + } +}; + + + + +function Item(props) { + const { sx, ...other } = props; + return ( + (theme.palette.mode === 'dark' ? '#101010' : '#fff'), + color: (theme) => (theme.palette.mode === 'dark' ? 'grey.300' : 'grey.800'), + border: '1px solid', + borderColor: (theme) => + theme.palette.mode === 'dark' ? 'grey.800' : 'grey.300', + p: 1, + m: 1, + borderRadius: 2, + fontSize: '0.875rem', + fontWeight: '700', + ...sx, + }} + {...other} + /> + ); +} + +Item.propTypes = { + sx: PropTypes.oneOfType([ + PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool]), + ), + PropTypes.func, + PropTypes.object, + ]), +}; + + + +let columnsData = [ + + { title: 'Foto', field: 'ticket.contact.profilePicUrl', render: rowData => }, + + { title: 'Nome', field: 'ticket.contact.name' }, + { title: 'Contato', field: 'ticket.contact.number' }, + { title: 'schedulingTime', field: 'schedulingTime' }, + { title: 'schedulingDate', field: 'schedulingDate' }, + { title: 'message', field: 'message' }, + + ]; + + + + +const SchedulesReminder = () => { + + const { user: userA } = useContext(AuthContext); + + //-------- + const [searchParam] = useState(""); + //const [loading, setLoading] = useState(false); + //const [hasMore, setHasMore] = useState(false); + const [pageNumber, setPageNumber] = useState(1); + const [users, dispatch] = useReducer(reducer, []); + //const [columns, setColums] = useState([]) + const [startDate, setDatePicker1] = useState(new Date()) + const [endDate, setDatePicker2] = useState(new Date()) + const [userId, setUser] = useState(null) + const [query, dispatchQ] = useReducer(reducerQ, []) + const [contactNumber, setContactNumber] = useState(""); + + const [resetChild, setReset] = useState(false) + + const [selectedSchedule, setSelectedSchedule] = useState(null); + const [confirmModalOpen, setConfirmModalOpen] = useState(false); + const [dataRows, setData] = useState([]); + + + useEffect(() => { + dispatch({ type: "RESET" }); + dispatchQ({ type: "RESET" }) + + setPageNumber(1); + }, [searchParam]); + + useEffect(() => { + //setLoading(true); + + const delayDebounceFn = setTimeout(() => { + + const fetchUsers = async () => { + try { + const { data } = await api.get("/users/", { + params: { searchParam, pageNumber }, + }); + + dispatch({ type: "LOAD_USERS", payload: data.users }); + //setHasMore(data.hasMore); + //setLoading(false); + } catch (err) { + console.log(err); + } + }; + + fetchUsers(); + + }, 500); + return () => clearTimeout(delayDebounceFn); + }, [searchParam, pageNumber]); + + useEffect(() => { + + setData(query) + + }, [query]) + + useEffect(() => { + //setLoading(true); + + const delayDebounceFn = setTimeout(() => { + + const fetchQueries = async () => { + try { + + const dataQuery = await api.get("/schedules/", {params: {contactNumber, startDate, endDate },}); + + dispatchQ({ type: "RESET" }) + dispatchQ({ type: "LOAD_QUERY", payload: dataQuery.data }); + + //setLoading(false); + + } catch (err) { + console.log(err); + } + }; + + fetchQueries(); + + }, 500); + return () => clearTimeout(delayDebounceFn); + + }, [contactNumber, startDate, endDate]); + + +// Get from child 1 +const datePicker1Value = (data) => { + console.log('DATE1: ',(data)); + setDatePicker1(data) +} + +// Get from child 2 +const datePicker2Value = (data) => { + console.log('DATE2: ',(data)); + setDatePicker2(data) +} + +// Get from child 3 +const textFieldSelectUser = (data) => { + console.log('textField: ',data); + setUser(data) +} + +const handleSearch = (event) => { + setContactNumber(event.target.value.toLowerCase()); +}; + +const handleClear = () => { + + setContactNumber('') + + setReset(true) + +} + +const handleCloseConfirmationModal = () => { + setConfirmModalOpen(false); + setSelectedSchedule(null); +}; + + +const handleDeleteRows = (id) => { + + let _data = [...dataRows]; + + _data.forEach(rd => { + _data = _data.filter(t => t.id !== id); + }); + setData(_data); + +}; + + +const handleDeleteSchedule = async (scheduleId) => { + try { + await api.delete(`/schedule/${scheduleId}`); + toast.success(("Lembrete deletado com sucesso!")); + handleDeleteRows(scheduleId) + } catch (err) { + toastError(err); + } + setSelectedSchedule(null); +}; + + return ( + + + + + + + + + + ), + }} + /> + + + {/* + { + return {'value': obj.id, 'label': obj.name} + })}/> + + */} + + + + + + {/* */} + + + + + + + + + + + + + {/* */} + + handleDeleteSchedule(selectedSchedule.id)} + > + Deseja realmente deletar esse Lembrete? + + + console.log("You saved ",rowData) + }, + { + icon: Delete, + tooltip: 'Deletar', + onClick: (event, rowData) => { + console.log("You want to delete ",rowData) + setSelectedSchedule(rowData); + setConfirmModalOpen(true); + } + // onClick: handleDeleteRows + } + ]} + + options={{ + search: true, + selection: false, + paging: false, + padding: 'dense', + sorting: true, + //loadingType: 'linear', + searchFieldStyle: { + width: 300, + }, + + }} + /> + + + + + + + ) + + +}; + +export default SchedulesReminder; diff --git a/frontend/src/routes/index.js b/frontend/src/routes/index.js index f05b0b6..ad0b229 100644 --- a/frontend/src/routes/index.js +++ b/frontend/src/routes/index.js @@ -6,6 +6,7 @@ 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/"; @@ -19,8 +20,7 @@ import Queues from "../pages/Queues/"; import { AuthProvider } from "../context/Auth/AuthContext"; import { WhatsAppsProvider } from "../context/WhatsApp/WhatsAppsContext"; import Route from "./Route"; - -//import { Report } from "@material-ui/icons"; + //console.log('---AuthProvider: ',AuthProvider) @@ -58,9 +58,13 @@ const Routes = () => { component={Report} isPrivate /> - + + + + +