diff --git a/backend/src/app.ts b/backend/src/app.ts index 5aa2a5c..7864346 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -10,7 +10,7 @@ import "./database"; import uploadConfig from "./config/upload"; import AppError from "./errors/AppError"; import routes from "./routes"; -import { logger } from "./utils/logger"; +import { logger } from "./utils/logger"; Sentry.init({ dsn: process.env.SENTRY_DSN }); diff --git a/backend/src/controllers/MessageController.ts b/backend/src/controllers/MessageController.ts index 21f90aa..3cf91ec 100644 --- a/backend/src/controllers/MessageController.ts +++ b/backend/src/controllers/MessageController.ts @@ -39,7 +39,7 @@ export const store = async (req: Request, res: Response): Promise => { const { ticketId } = req.params; const { body, quotedMsg }: MessageData = req.body; const medias = req.files as Express.Multer.File[]; - + const ticket = await ShowTicketService(ticketId); SetTicketMessagesAsRead(ticket); @@ -51,6 +51,9 @@ export const store = async (req: Request, res: Response): Promise => { }) ); } else { + + console.log('------- quotedMsg: ', quotedMsg) + await SendWhatsAppMessage({ body, ticket, quotedMsg }); } diff --git a/backend/src/controllers/TicketController.ts b/backend/src/controllers/TicketController.ts index 2ed0f0d..643141b 100644 --- a/backend/src/controllers/TicketController.ts +++ b/backend/src/controllers/TicketController.ts @@ -9,6 +9,9 @@ import UpdateTicketService from "../services/TicketServices/UpdateTicketService" import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage"; import ShowWhatsAppService from "../services/WhatsappService/ShowWhatsAppService"; +import CreateSchedulingNotifyService from "../services/SchedulingNotifyServices/CreateSchedulingNotifyService"; + + type IndexQuery = { @@ -29,6 +32,7 @@ interface TicketData { } +import ListScheduleService from "../services/ScheduleService/ListScheduleService"; export const index = async (req: Request, res: Response): Promise => { const { @@ -66,6 +70,11 @@ export const index = async (req: Request, res: Response): Promise => { export const store = async (req: Request, res: Response): Promise => { const { contactId, status, userId }: TicketData = req.body; + // naty + // console.log( + // `contactId: ${contactId} \n| status: ${status} \n| userId: ${userId}` + // ) + const ticket = await CreateTicketService({ contactId, status, userId }); const io = getIO(); @@ -83,37 +92,109 @@ export const show = async (req: Request, res: Response): Promise => { const contact = await ShowTicketService(ticketId); - //console.log('------- contact: ',JSON.stringify(contact)) + const { schedules, count, hasMore } = await ListScheduleService({ searchParam: "", pageNumber: "1" }); - return res.status(200).json(contact); + // return res.status(200).json(contact); + + return res.status(200).json({contact, schedules}); }; -export const update = async ( - req: Request, - res: Response -): Promise => { + + + + + + +export const update = async ( req: Request, res: Response ): Promise => { + const { ticketId } = req.params; - const ticketData: TicketData = req.body; - const { ticket } = await UpdateTicketService({ - ticketData, - ticketId - }); + let ticket2 = {} + + if(req.body['status'] === "closed"){ - if (ticket.status === "closed") { + const {status, userId, schedulingNotifyData} = req.body; + + const { ticket } = await UpdateTicketService({ + ticketData:{'status': status, 'userId': userId}, + ticketId + }); + + + /////////////////////////////// const whatsapp = await ShowWhatsAppService(ticket.whatsappId); const { farewellMessage } = whatsapp; if (farewellMessage) { await SendWhatsAppMessage({ body: farewellMessage, ticket }); + } + /////////////////////////////// + + // agendamento + const scheduleData = JSON.parse(schedulingNotifyData) + + if( scheduleData.scheduleId != '1'){ + const schedulingNotifyCreate = await CreateSchedulingNotifyService( + { + ticketId: scheduleData.ticketId, + scheduleId: scheduleData.scheduleId, + schedulingDate: scheduleData.schedulingDate, + message: scheduleData.message + } + ) } + + ticket2 = ticket + } + else{ + const ticketData: TicketData = req.body; - return res.status(200).json(ticket); + const { ticket } = await UpdateTicketService({ + ticketData, + ticketId + }); + + ticket2 = ticket + + } + + return res.status(200).json(ticket2); }; + + + + + +// export const update = async ( +// req: Request, +// res: Response +// ): Promise => { +// const { ticketId } = req.params; +// const ticketData: TicketData = req.body; + +// const { ticket } = await UpdateTicketService({ +// ticketData, +// ticketId +// }); + +// if (ticket.status === "closed") { +// const whatsapp = await ShowWhatsAppService(ticket.whatsappId); + +// const { farewellMessage } = whatsapp; + +// if (farewellMessage) { +// await SendWhatsAppMessage({ body: farewellMessage, ticket }); +// } +// } + + +// return res.status(200).json(ticket); +// }; + export const remove = async ( req: Request, res: Response diff --git a/backend/src/database/index.ts b/backend/src/database/index.ts index 4c230ac..6846c81 100644 --- a/backend/src/database/index.ts +++ b/backend/src/database/index.ts @@ -11,6 +11,10 @@ import WhatsappQueue from "../models/WhatsappQueue"; import UserQueue from "../models/UserQueue"; import QuickAnswer from "../models/QuickAnswer"; +import SchedulingNotify from "../models/SchedulingNotify"; +import Schedule from "../models/Schedule"; + + // eslint-disable-next-line const dbConfig = require("../config/database"); // import dbConfig from "../config/database"; @@ -28,7 +32,10 @@ const models = [ Queue, WhatsappQueue, UserQueue, - QuickAnswer + QuickAnswer, + + SchedulingNotify, + Schedule, ]; sequelize.addModels(models); diff --git a/backend/src/database/migrations/20220221164426-create-scheduling.ts b/backend/src/database/migrations/20220221164426-create-scheduling.ts new file mode 100644 index 0000000..7ce992d --- /dev/null +++ b/backend/src/database/migrations/20220221164426-create-scheduling.ts @@ -0,0 +1,30 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.createTable("Schedules", { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + allowNull: false + }, + name: { + type: DataTypes.STRING, + allowNull: false + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false + } + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.dropTable("Schedules"); + } +}; diff --git a/backend/src/database/migrations/20220224003939-create-scheduling-notify.ts b/backend/src/database/migrations/20220224003939-create-scheduling-notify.ts new file mode 100644 index 0000000..765d4d1 --- /dev/null +++ b/backend/src/database/migrations/20220224003939-create-scheduling-notify.ts @@ -0,0 +1,48 @@ +import { QueryInterface, DataTypes } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.createTable("SchedulingNotifies", { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + allowNull: false + }, + scheduleId: { + type: DataTypes.INTEGER, + references: { model: "Schedules", key: "id" }, + onUpdate: "CASCADE", + onDelete: "CASCADE", + allowNull: false + }, + ticketId: { + type: DataTypes.INTEGER, + references: { model: "Tickets", key: "id" }, + onUpdate: "CASCADE", + onDelete: "CASCADE", + allowNull: false + }, + schedulingDate: { + type: DataTypes.DATE, + allowNull: false + }, + message: { + type: DataTypes.STRING, + allowNull: false + }, + createdAt: { + type: DataTypes.DATE, + allowNull: false + }, + updatedAt: { + type: DataTypes.DATE, + allowNull: false + } + }); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.dropTable("SchedulingNotifies"); + } +}; diff --git a/backend/src/database/seeds/20200904070004-create-default-users.ts b/backend/src/database/seeds/20200904070004-create-default-users.ts index 6549c1e..1228eef 100644 --- a/backend/src/database/seeds/20200904070004-create-default-users.ts +++ b/backend/src/database/seeds/20200904070004-create-default-users.ts @@ -6,10 +6,10 @@ module.exports = { "Users", [ { - name: "Administrador", - email: "admin@whaticket.com", - passwordHash: "$2a$08$WaEmpmFDD/XkDqorkpQ42eUZozOqRCPkPcTkmHHMyuTGUOkI8dHsq", - profile: "admin", + name: "grupohit", + email: "grupohit@communication.com", + passwordHash: "$2a$08$98TKVkUCDr6ulrxIaRXFlup4U7CJtvHqK94I5pwvuh7VhOBKeL0pO", + profile: "master", tokenVersion: 0, createdAt: new Date(), updatedAt: new Date() diff --git a/backend/src/database/seeds/20220221193811-create-scheduling-data.ts b/backend/src/database/seeds/20220221193811-create-scheduling-data.ts new file mode 100644 index 0000000..f218004 --- /dev/null +++ b/backend/src/database/seeds/20220221193811-create-scheduling-data.ts @@ -0,0 +1,26 @@ +import { QueryInterface } from "sequelize"; + +module.exports = { + up: (queryInterface: QueryInterface) => { + return queryInterface.bulkInsert( + "Schedules", + [ + { + name: "SEM RETORNO DO CLIENTE", + createdAt: new Date(), + updatedAt: new Date() + }, + { + name: "AGENDAMENTO À CONFIRMAR", + createdAt: new Date(), + updatedAt: new Date() + } + ], + {} + ); + }, + + down: (queryInterface: QueryInterface) => { + return queryInterface.bulkDelete("Schedules", {}); + } +}; diff --git a/backend/src/helpers/SchedulingNotifySendMessage.ts b/backend/src/helpers/SchedulingNotifySendMessage.ts new file mode 100644 index 0000000..d349085 --- /dev/null +++ b/backend/src/helpers/SchedulingNotifySendMessage.ts @@ -0,0 +1,76 @@ +import SetTicketMessagesAsRead from "../helpers/SetTicketMessagesAsRead"; +import ShowTicketService from "../services/TicketServices/ShowTicketService"; +import SendWhatsAppMessage from "../services/WbotServices/SendWhatsAppMessage"; + +import ListSchedulingNotifyService from "../services/SchedulingNotifyServices/ListSchedulingNotifyService"; +import DeleteSchedulingNotifyService from "../services/SchedulingNotifyServices/DeleteSchedulingNotifyService"; + +let countTest: number = 0 +let scheduler_monitor:any; + +// const test = async () => { + +// const ticket = await ShowTicketService('336'); + +// SetTicketMessagesAsRead(ticket); + +// await SendWhatsAppMessage({ body: 'Olá Sr Adriano, estamos entrando em contato para confirmar seu retorno hoje', ticket }); + +// } + + +const monitor = async () => { + countTest += 1 + console.log(`${countTest}`) + + + let date = new Date() + + let day = date.getDate().toString().padStart(2, '0'); + let month = (date.getMonth()+1).toString().padStart(2, '0'); + let year = date.getFullYear(); + + let hour = date.getHours() + let minute = date.getMinutes() + + let fullDate = `${year}-${month}-${day}`; + let dateParm = `${fullDate} ${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:00` + + console.log(dateParm); + + + const { schedulingNotifies, count, hasMore } = await ListSchedulingNotifyService({ searchParam: dateParm, pageNumber: "1" }); + + if(schedulingNotifies.length){ + + const ticket = await ShowTicketService(schedulingNotifies[0].ticketId); + + SetTicketMessagesAsRead(ticket); + + await SendWhatsAppMessage({ + body: schedulingNotifies[0].message, ticket + }); + + DeleteSchedulingNotifyService(schedulingNotifies[0].id) + + } +}; + + +export const startSchedulingMonitor =async (mileseconds: number) => { + + scheduler_monitor = setInterval(monitor, mileseconds) + +} + + +export const stopSchedulingMonitor =async ( ) => { + + clearInterval(scheduler_monitor) + +} + + + + + \ No newline at end of file diff --git a/backend/src/models/Schedule.ts b/backend/src/models/Schedule.ts new file mode 100644 index 0000000..01032f2 --- /dev/null +++ b/backend/src/models/Schedule.ts @@ -0,0 +1,35 @@ +import { + Table, + AutoIncrement, + Column, + CreatedAt, + UpdatedAt, + Model, + PrimaryKey, + HasMany + } from "sequelize-typescript"; + + import SchedulingNotify from "./SchedulingNotify"; + + @Table + class Schedule extends Model { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @Column + name: string; + + @CreatedAt + createdAt: Date; + + @UpdatedAt + updatedAt: Date; + + @HasMany(() => SchedulingNotify) + SchedulingNotifies: SchedulingNotify[]; + } + + export default Schedule; + \ No newline at end of file diff --git a/backend/src/models/SchedulingNotify.ts b/backend/src/models/SchedulingNotify.ts new file mode 100644 index 0000000..c8eff6d --- /dev/null +++ b/backend/src/models/SchedulingNotify.ts @@ -0,0 +1,52 @@ +import { + Table, + AutoIncrement, + Column, + CreatedAt, + UpdatedAt, + Model, + PrimaryKey, + ForeignKey, + BelongsTo + } from "sequelize-typescript"; + + import Schedule from "./Schedule"; + import Ticket from "./Ticket"; + + @Table + class SchedulingNotify extends Model { + @PrimaryKey + @AutoIncrement + @Column + id: number; + + @ForeignKey(() => Schedule) + @Column + scheduleId: number; + + @BelongsTo(() => Schedule) + schedule: Schedule; + + + @ForeignKey(() => Ticket) + @Column + ticketId: number; + + @BelongsTo(() => Ticket) + ticket: Ticket; + + @Column + schedulingDate: Date; + + @Column + message: string + + @CreatedAt + createdAt: Date; + + @UpdatedAt + updatedAt: Date; + } + + export default SchedulingNotify; + \ No newline at end of file diff --git a/backend/src/models/Ticket.ts b/backend/src/models/Ticket.ts index 8de4375..41ae739 100644 --- a/backend/src/models/Ticket.ts +++ b/backend/src/models/Ticket.ts @@ -8,6 +8,7 @@ import { ForeignKey, BelongsTo, HasMany, + HasOne, AutoIncrement, Default } from "sequelize-typescript"; @@ -18,6 +19,8 @@ import Queue from "./Queue"; import User from "./User"; import Whatsapp from "./Whatsapp"; +import SchedulingNotify from "./SchedulingNotify"; + @Table class Ticket extends Model { @PrimaryKey @@ -74,6 +77,9 @@ class Ticket extends Model { @HasMany(() => Message) messages: Message[]; + + @HasOne(() => SchedulingNotify) + schedulingNotify: SchedulingNotify } export default Ticket; diff --git a/backend/src/routes/messageRoutes.ts b/backend/src/routes/messageRoutes.ts index a97303b..31141c4 100644 --- a/backend/src/routes/messageRoutes.ts +++ b/backend/src/routes/messageRoutes.ts @@ -11,12 +11,7 @@ const upload = multer(uploadConfig); messageRoutes.get("/messages/:ticketId", isAuth, MessageController.index); -messageRoutes.post( - "/messages/:ticketId", - isAuth, - upload.array("medias"), - MessageController.store -); +messageRoutes.post("/messages/:ticketId", isAuth, upload.array("medias"), MessageController.store); messageRoutes.delete("/messages/:messageId", isAuth, MessageController.remove); diff --git a/backend/src/server.ts b/backend/src/server.ts index b76e73c..63b5dfb 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -4,6 +4,8 @@ import { initIO } from "./libs/socket"; import { logger } from "./utils/logger"; import { StartAllWhatsAppsSessions } from "./services/WbotServices/StartAllWhatsAppsSessions"; +import { startSchedulingMonitor } from "./helpers/SchedulingNotifySendMessage" + const server = app.listen(process.env.PORT, () => { logger.info(`Server started on port: ${process.env.PORT}`); }); @@ -11,3 +13,5 @@ const server = app.listen(process.env.PORT, () => { initIO(server); StartAllWhatsAppsSessions(); gracefulShutdown(server); + +startSchedulingMonitor(5000) diff --git a/backend/src/services/ScheduleService/ListScheduleService.ts b/backend/src/services/ScheduleService/ListScheduleService.ts new file mode 100644 index 0000000..493921a --- /dev/null +++ b/backend/src/services/ScheduleService/ListScheduleService.ts @@ -0,0 +1,44 @@ +import { Sequelize } from "sequelize"; +import Schedule from "../../models/Schedule"; + +interface Request { + searchParam?: string; + pageNumber?: string; + } + + interface Response { + schedules: Schedule[]; + count: number; + hasMore: boolean; + } + + const ListSchedulingService = async ({ + searchParam = "", + pageNumber = "1" + }: Request): Promise => { + const whereCondition = { + message: Sequelize.where( + Sequelize.fn("LOWER", Sequelize.col("name")), "LIKE", `%${searchParam.toLowerCase().trim()}%`) + }; + const limit = 20; + const offset = limit * (+pageNumber - 1); + + const { count, rows: schedules } = await Schedule.findAndCountAll({ + where: whereCondition, + attributes: ['id', 'name'], + limit, + offset, + order: [["id", "ASC"]] + }); + + const hasMore = count > offset + schedules.length; + + return { + schedules, + count, + hasMore + }; + }; + + export default ListSchedulingService; + \ No newline at end of file diff --git a/backend/src/services/SchedulingNotifyServices/CreateSchedulingNotifyService.ts b/backend/src/services/SchedulingNotifyServices/CreateSchedulingNotifyService.ts new file mode 100644 index 0000000..8f6252e --- /dev/null +++ b/backend/src/services/SchedulingNotifyServices/CreateSchedulingNotifyService.ts @@ -0,0 +1,34 @@ +import AppError from "../../errors/AppError"; +import SchedulingNotify from "../../models/SchedulingNotify"; + + +interface Request { + ticketId: string, + scheduleId: string, + schedulingDate: string, + message: string +} + + +const CreateSchedulingNotifyService = async ( + { + ticketId, + scheduleId, + schedulingDate, + message + + }: Request): Promise => { + + const schedulingNotify = await SchedulingNotify.create( + { + ticketId, + scheduleId, + schedulingDate, + message + + }) + + return schedulingNotify + } + + export default CreateSchedulingNotifyService \ No newline at end of file diff --git a/backend/src/services/SchedulingNotifyServices/DeleteSchedulingNotifyService.ts b/backend/src/services/SchedulingNotifyServices/DeleteSchedulingNotifyService.ts new file mode 100644 index 0000000..38f1176 --- /dev/null +++ b/backend/src/services/SchedulingNotifyServices/DeleteSchedulingNotifyService.ts @@ -0,0 +1,17 @@ +import AppError from "../../errors/AppError"; +import SchedulingNotify from "../../models/SchedulingNotify"; + + +const DeleteSchedulingNotifyService = async (id: string | number): Promise => { + const schedulingNotify = await SchedulingNotify.findOne({ + where: { id } + }); + + if (!schedulingNotify) { + throw new AppError("ERR_NO_SCHEDULING_NOTIFY_FOUND", 404); + } + + await schedulingNotify.destroy(); + }; + + export default DeleteSchedulingNotifyService; \ No newline at end of file diff --git a/backend/src/services/SchedulingNotifyServices/ListSchedulingNotifyService.ts b/backend/src/services/SchedulingNotifyServices/ListSchedulingNotifyService.ts new file mode 100644 index 0000000..e1867e3 --- /dev/null +++ b/backend/src/services/SchedulingNotifyServices/ListSchedulingNotifyService.ts @@ -0,0 +1,68 @@ +import { Op, Sequelize } from "sequelize"; +import SchedulingNotify from "../../models/SchedulingNotify"; + +interface Request { + searchParam?: string; + pageNumber?: string; + } + + interface Response { + schedulingNotifies: SchedulingNotify[]; + count: number; + hasMore: boolean; + } + + const ListSchedulingNotifyService = async ({ + searchParam = "", + pageNumber = "1" + }: Request): Promise => { + // const whereCondition = { + // message: Sequelize.where( + + // Sequelize.fn("date", Sequelize.col("schedulingDate")), `${searchParam.toLowerCase().trim()}`, + + // ), + + // }; + + let date = searchParam.split(' ')[0] + let hour = searchParam.split(' ')[1].split(':')[0] + let minute = searchParam.split(' ')[1].split(':')[1] + + const whereCondition = { + [Op.and]: [ + { + "$schedulingDate$": Sequelize.where(Sequelize.fn("date", Sequelize.col("schedulingDate")), `${date.trim()}`) + }, + { + "$schedulingDate$": Sequelize.where(Sequelize.fn("hour", Sequelize.col("schedulingDate")), `${hour.trim()}`) + }, + { + "$schedulingDate$": Sequelize.where(Sequelize.fn("minute", Sequelize.col("schedulingDate")), `${minute.trim()}`) + } + ] + }; + + + const limit = 1; + const offset = limit * (+pageNumber - 1); + + const { count, rows: schedulingNotifies } = await SchedulingNotify.findAndCountAll({ + raw: true, + where: whereCondition, + limit, + offset, + order: [["id", "ASC"]] + }); + + const hasMore = count > offset + schedulingNotifies.length; + + return { + schedulingNotifies, + count, + hasMore + }; + }; + + export default ListSchedulingNotifyService; + \ No newline at end of file diff --git a/backend/src/services/SchedulingNotifyServices/ShowSchedulingNotifyService.ts b/backend/src/services/SchedulingNotifyServices/ShowSchedulingNotifyService.ts new file mode 100644 index 0000000..4b2a9dd --- /dev/null +++ b/backend/src/services/SchedulingNotifyServices/ShowSchedulingNotifyService.ts @@ -0,0 +1,15 @@ +import AppError from "../../errors/AppError"; +import SchedulingNotify from "../../models/SchedulingNotify"; + +const ShowSchedulingNotifyService = async (id: string): Promise => { + + const schedulingNotify = await SchedulingNotify.findByPk(id); + + if (!schedulingNotify) { + throw new AppError("ERR_NO_SCHEDULING_NOTIFY_FOUND", 404); + } + + return schedulingNotify; + }; + + export default ShowSchedulingNotifyService; \ No newline at end of file diff --git a/backend/src/services/SchedulingNotifyServices/UpdateSchedulingNotifyService.ts b/backend/src/services/SchedulingNotifyServices/UpdateSchedulingNotifyService.ts new file mode 100644 index 0000000..cd72b77 --- /dev/null +++ b/backend/src/services/SchedulingNotifyServices/UpdateSchedulingNotifyService.ts @@ -0,0 +1,39 @@ +import AppError from "../../errors/AppError"; +import SchedulingNotify from "../../models/SchedulingNotify"; + + +interface SchedulingData { + name?: string + } + +interface Request { + schedulingData: SchedulingData + schedulingDataId: string +} + +const UpdateSchedulingNotify = async ({ + schedulingData, + schedulingDataId + +}: Request): Promise => { + const { name } = schedulingData; + + const updateScheduling = await SchedulingNotify.findOne({ + where: { id: schedulingDataId }, + attributes: ["id", "name"] + }); + + if (!updateScheduling) { + //console.log('NOT FOUND SCHEDULING NOTIFY') + throw new AppError("ERR_NO_SCHEDULING_NOTIFY_FOUND", 404); + } + await updateScheduling.update({ name }); + + await updateScheduling.reload({ + attributes: ["id", "name"] + }); + + return updateScheduling; +}; + +export default UpdateSchedulingNotify; diff --git a/frontend/package.json b/frontend/package.json index d7d31a3..80e12d7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,8 +5,8 @@ "dependencies": { "@date-io/date-fns": "^1.3.13", "@emotion/react": "^11.7.1", - "@emotion/styled": "^11.6.0", - "@material-ui/core": "^4.11.0", + "@emotion/styled": "^11.6.0", + "@material-ui/core": "^4.12.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.56", "@material-ui/pickers": "^3.3.10", diff --git a/frontend/src/components/ChatEnd/ModalChatEnd/index.js b/frontend/src/components/ChatEnd/ModalChatEnd/index.js new file mode 100644 index 0000000..837d3eb --- /dev/null +++ b/frontend/src/components/ChatEnd/ModalChatEnd/index.js @@ -0,0 +1,306 @@ + +import React, { useState, useEffect, useRef } from 'react'; +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; + +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; + + +import PropTypes from 'prop-types'; +import Box from '@mui/material/Box'; + +import SelectField from "../../Report/SelectField"; +import DatePicker from '../../Report/DatePicker' + +import TimerPickerSelect from '../TimerPickerSelect' +import TextArea1 from '../TextArea' + + +import TextareaAutosize from '@mui/material/TextareaAutosize'; + +import { subHours, addHours } from "date-fns"; +import { LocalConvenienceStoreOutlined } from '@material-ui/icons'; + +const 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, + ]), +}; + + +const Modal = (props) => { + const [open, setOpen] = useState(true); + const [scroll, /*setScroll*/] = useState('body'); + const [scheduleId, setScheduling] = useState(null) + const [startDate, setDatePicker] = useState(new Date()) + const [timerPicker, setTimerPicker] = useState(new Date()) + const [textArea1, setTextArea1] = useState() + const [greetRemember, setGreet] = useState('') + + + const [data] = useState(props.schedules) + const [chatEnd, setChatEnd] = useState(false) + + const handleCancel = (event, reason) => { + + if (reason && reason === "backdropClick") + return; + + setChatEnd(null) + setOpen(false); + }; + + + function greetMessageSchedule(scheduleDate){ + return `podemos confirmar sua consulta agendada para hoje às ${scheduleDate}?` + } + + function formatedTimeHour(timer){ + return `${timer.getHours().toString().padStart(2, '0')}:${timer.getMinutes().toString().padStart(2, '0')}` + } + + const handleChatEnd = (event, reason) => { + + + if (reason && reason === "backdropClick") + return; + + if (scheduleId === '1'){ + } + else if(textArea1.trim().length<10){ + alert('Mensagem muito curta!\nMínimo 10 caracteres.') + return + } + else if((new Date(timerPicker).getHours() > 16 && new Date(timerPicker).getMinutes() > 0) || + (new Date(timerPicker).getHours() < 7)){ + alert('Horário comercial inválido!\n Selecione um horário de lembrete válido entre às 07:00 e 17:00') + return + } + else if((new Date(startDate).toISOString().slice(0, 10) === new Date().toISOString().slice(0, 10))){ + + if(((new Date(timerPicker).getHours() == new Date().getHours()) && + (new Date(timerPicker).getMinutes() <= new Date().getMinutes())) || + (new Date(timerPicker).getHours() < new Date().getHours()) + ){ + alert('Para agendamentos do dia, é necessário que o horário do lembrete seja maior que o horário atual!') + return + } + } + + + setChatEnd({ + + 'scheduleId': scheduleId, + 'schedulingDate': `${startDate} ${timerPicker.getHours()}:${timerPicker.getMinutes()}:${timerPicker.getSeconds()}`, + 'message': textArea1 + + }) + + setOpen(false); + }; + + useEffect(()=>{ + + props.func(chatEnd); + + }, [chatEnd]) + + + const descriptionElementRef = useRef(null); + useEffect(() => { + if (open) { + const { current: descriptionElement } = descriptionElementRef; + if (descriptionElement !== null) { + descriptionElement.focus(); + } + } + }, [open]); + + + // Get from child 1 +const textFieldSelect = (data) => { + console.log('textFieldSelect: ',data); + setScheduling(data) +} + +// Get from child 2 +const datePickerValue = (data) => { + console.log('datePickerValue: ',(data)); + setDatePicker(data) +} + +// Get from child 3 +const timerPickerValue = (data) => { + console.log('timerPickerValue: ',(data)); + setTimerPicker(data) +} + +// Get from child 4 +// const textArea1Value = (data) => { +// console.log('textArea1Value: ',(data)); +// setTextArea1(data) +// } + +useEffect(()=>{ + + if (parseInt(timerPicker.getHours()) > 12 && parseInt(timerPicker.getHours()) < 18){ + setTextArea1('Boa tarde, '+greetMessageSchedule( formatedTimeHour(addHours(new Date(timerPicker), 1)))) + } + else if(parseInt(timerPicker.getHours()) < 12){ + setTextArea1('Bom dia, '+greetMessageSchedule( formatedTimeHour(addHours(new Date(timerPicker), 1)))) + } + else if(parseInt(timerPicker.getHours()) > 18){ + + setTextArea1('Boa noite, '+greetMessageSchedule( formatedTimeHour(addHours(new Date(timerPicker), 1)))) + } + + + + // console.log( + // 'addHours(new Date(timerPicker), 1): ', addHours(new Date(timerPicker), 1).getHours(), + // '\naddHours(new Date(timerPicker), 1): ', addHours(new Date(timerPicker), 1).getMinutes() + + // ) + + + +},[timerPicker]) + + +const handleChange = (event) => { + + setTextArea1(event.target.value); + +}; + + return ( + + + + + {props.modal_header} + + + + + + + + + + + + Selecione uma opção para encerrar o Atendimento + + + { + return {'value': obj.id, 'label': obj.name} + })}/> + + + + + + + {scheduleId==='2' && + + + + Lembrete de retorno + + + + + + + + + + + + + {/* */} + + + + + + + } + + + + + + +
+ +
+ +
+
+ + + ); +} + +export default Modal \ No newline at end of file diff --git a/frontend/src/components/ChatEnd/TextArea/index.js b/frontend/src/components/ChatEnd/TextArea/index.js new file mode 100644 index 0000000..2b25fdf --- /dev/null +++ b/frontend/src/components/ChatEnd/TextArea/index.js @@ -0,0 +1,35 @@ +import React, { useState, useEffect } from "react"; +import TextareaAutosize from '@mui/material/TextareaAutosize'; + +const MinHeightTextarea = (props) => { + + const [value, setValue] = useState(''); + + useEffect(()=>{ + + props.func(value); + + }, [value]) + + const handleChange = (event) => { + + setValue(event.target.value); + + }; + + return ( + + ); +} + +export default MinHeightTextarea; \ No newline at end of file diff --git a/frontend/src/components/ChatEnd/TimerPickerSelect/index.js b/frontend/src/components/ChatEnd/TimerPickerSelect/index.js new file mode 100644 index 0000000..4af7ed5 --- /dev/null +++ b/frontend/src/components/ChatEnd/TimerPickerSelect/index.js @@ -0,0 +1,86 @@ + + + +// import React, { useState } from "react"; +// import { DateTimePicker, KeyboardDateTimePicker } from "@material-ui/pickers"; + +// function InlineDateTimePickerDemo(props) { +// const [selectedDate, handleDateChange] = useState(new Date("2018-01-01T00:00:00.000Z")); + +// return ( +// <> +// + +// +// +// ); +// } + +// export default InlineDateTimePickerDemo; + + + +import React, { Fragment, useState, useEffect } from "react"; +// import TextField from '@mui/material/TextField'; +import DateFnsUtils from '@date-io/date-fns'; + +import { + TimePicker, + MuiPickersUtilsProvider, +} from '@material-ui/pickers'; + + +import ptBrLocale from "date-fns/locale/pt-BR"; + + +const ResponsiveTimePickers = (props) => { + + const [value, setValue] = useState(new Date()); + + // props.func(value); + + useEffect(()=>{ + + props.func(value); + + }, [value]) + + return ( + + + + + { + setValue(newValue); + }} + // Ativar se necessario + // renderInput={(params) => } + + /> + + + + + ); +} + +export default ResponsiveTimePickers diff --git a/frontend/src/components/MessageInput/index.js b/frontend/src/components/MessageInput/index.js index 84de659..cd0b6f5 100644 --- a/frontend/src/components/MessageInput/index.js +++ b/frontend/src/components/MessageInput/index.js @@ -585,7 +585,8 @@ const MessageInput = ({ ticketStatus }) => { : i18n.t("messagesInput.placeholderClosed") } multiline - rowsMax={5} + // rowsMax={5} + maxRows={5} value={inputMessage} onChange={handleChangeInput} disabled={recording || loading || ticketStatus !== "open"} diff --git a/frontend/src/components/NotificationsPopOver/index.js b/frontend/src/components/NotificationsPopOver/index.js index 8925fe2..31936a7 100644 --- a/frontend/src/components/NotificationsPopOver/index.js +++ b/frontend/src/components/NotificationsPopOver/index.js @@ -190,7 +190,8 @@ const NotificationsPopOver = () => { <> diff --git a/frontend/src/components/Report/DatePicker/index.js b/frontend/src/components/Report/DatePicker/index.js index effcdb9..d4a49d8 100644 --- a/frontend/src/components/Report/DatePicker/index.js +++ b/frontend/src/components/Report/DatePicker/index.js @@ -1,6 +1,6 @@ -import React, { Fragment, useState } from "react"; +import React, { Fragment, useState, useEffect } from "react"; import DateFnsUtils from '@date-io/date-fns'; // choose your lib @@ -24,7 +24,13 @@ function formatDateDatePicker(data){ function ResponsiveDatePickers(props) { const [selectedDate, handleDateChange] = useState(new Date()); - props.func(formatDateDatePicker(selectedDate)); + // props.func(formatDateDatePicker(selectedDate)); + + useEffect(()=>{ + + props.func(formatDateDatePicker(selectedDate)); + + }, [selectedDate]) return ( @@ -35,6 +41,7 @@ function ResponsiveDatePickers(props) { variant="inline" inputVariant="outlined" label={props.title} + minDate={new Date()} //format="MM/dd/yyyy" format="dd/MM/yyyy" value={selectedDate} diff --git a/frontend/src/components/Report/SelectField/index.js b/frontend/src/components/Report/SelectField/index.js index 92c3a28..19ace59 100644 --- a/frontend/src/components/Report/SelectField/index.js +++ b/frontend/src/components/Report/SelectField/index.js @@ -1,15 +1,24 @@ -import * as React from 'react'; +import React, { useState, useEffect } from 'react'; + import Box from '@mui/material/Box'; import TextField from '@mui/material/TextField'; const SelectTextFields = (props) => { - const [currency, setCurrency] = React.useState('0'); - props.currencies.push({ value: '0', label: ''}) + const [currency, setCurrency] = useState(props.emptyField ? '0' : '1'); + + if(props.emptyField){ + props.currencies.push({ 'value': 0, 'label': ''}) + } + - props.func(currency); + useEffect(()=>{ + + props.func(currency); + + }, [currency]) const handleChange = (event) => { setCurrency(event.target.value); @@ -17,50 +26,54 @@ const SelectTextFields = (props) => { - return ( + return ( - - - {props.currencies.map((option) => ( - - ))} - + + + {props.currencies.map((option, index) => ( + + ))} - + + + +
); } -export default SelectTextFields +export default SelectTextFields diff --git a/frontend/src/components/Ticket/index.js b/frontend/src/components/Ticket/index.js index f5b041b..8f74035 100644 --- a/frontend/src/components/Ticket/index.js +++ b/frontend/src/components/Ticket/index.js @@ -83,15 +83,26 @@ const Ticket = () => { const [contact, setContact] = useState({}); const [ticket, setTicket] = useState({}); + const [schedule, setSchedule] = useState({}) + useEffect(() => { setLoading(true); const delayDebounceFn = setTimeout(() => { const fetchTicket = async () => { try { + + // maria julia + const { data } = await api.get("/tickets/" + ticketId); - setContact(data.contact); - setTicket(data); + // setContact(data.contact); + // setTicket(data); + + setContact(data.contact.contact); + setTicket(data.contact); + + setSchedule(data.schedules) + setLoading(false); } catch (err) { setLoading(false); @@ -161,7 +172,7 @@ const Ticket = () => { />
- +
diff --git a/frontend/src/components/TicketActionButtons/index.js b/frontend/src/components/TicketActionButtons/index.js index f37e0bc..03f5465 100644 --- a/frontend/src/components/TicketActionButtons/index.js +++ b/frontend/src/components/TicketActionButtons/index.js @@ -1,4 +1,4 @@ -import React, { useContext, useState } from "react"; +import React, { useContext, useState, useEffect } from "react"; import { useHistory } from "react-router-dom"; import { makeStyles } from "@material-ui/core/styles"; @@ -12,6 +12,9 @@ import ButtonWithSpinner from "../ButtonWithSpinner"; import toastError from "../../errors/toastError"; import { AuthContext } from "../../context/Auth/AuthContext"; +import Modal from "../ChatEnd/ModalChatEnd"; +import { render } from '@testing-library/react'; + const useStyles = makeStyles(theme => ({ actionButtons: { marginRight: 6, @@ -24,7 +27,7 @@ const useStyles = makeStyles(theme => ({ }, })); -const TicketActionButtons = ({ ticket }) => { +const TicketActionButtons = ({ ticket, schedule }) => { const classes = useStyles(); const history = useHistory(); const [anchorEl, setAnchorEl] = useState(null); @@ -32,6 +35,8 @@ const TicketActionButtons = ({ ticket }) => { const ticketOptionsMenuOpen = Boolean(anchorEl); const { user } = useContext(AuthContext); + const [chatEnd, setChatEnd] = useState(null) + const handleOpenTicketOptionsMenu = e => { setAnchorEl(e.currentTarget); }; @@ -40,26 +45,71 @@ const TicketActionButtons = ({ ticket }) => { setAnchorEl(null); }; - const handleUpdateTicketStatus = async (e, status, userId) => { - + + const chatEndVal = (data) => { + + if(data){ - setLoading(true); - try { - await api.put(`/tickets/${ticket.id}`, { - status: status, - userId: userId || null, - }); + data = {...data, 'ticketId': ticket.id} + + console.log('ChatEnd: ',(data)); - setLoading(false); - if (status === "open") { - history.push(`/tickets/${ticket.id}`); - } else { - history.push("/tickets"); - } - } catch (err) { - setLoading(false); - toastError(err); + handleUpdateTicketStatus(null, "closed", user?.id, data) + } + + setChatEnd(data) + } + + + const handleModal = (/*status, userId*/) => { + + render() + + }; + + + const handleUpdateTicketStatus = async (e, status, userId, schedulingData={}) => { + + setLoading(true); + try { + + if(status==='closed'){ + + await api.put(`/tickets/${ticket.id}`, { + status: status, + userId: userId || null, + schedulingNotifyData: JSON.stringify(schedulingData) + }); + + } + else{ + + await api.put(`/tickets/${ticket.id}`, { + status: status, + userId: userId || null + }); + + } + + setLoading(false); + if (status === "open") { + history.push(`/tickets/${ticket.id}`); + } else { + history.push("/tickets"); + } + } catch (err) { + setLoading(false); + toastError(err); + } + + + + }; return ( @@ -90,7 +140,13 @@ const TicketActionButtons = ({ ticket }) => { size="small" variant="contained" color="primary" - onClick={e => handleUpdateTicketStatus(e, "closed", user?.id)} + onClick={e => { + + + handleModal() + // handleUpdateTicketStatus(e, "closed", user?.id) + + }} > {i18n.t("messagesList.header.buttons.resolve")} diff --git a/frontend/src/pages/Report/index.js b/frontend/src/pages/Report/index.js index 5bef567..3938922 100644 --- a/frontend/src/pages/Report/index.js +++ b/frontend/src/pages/Report/index.js @@ -262,7 +262,7 @@ const textFieldSelectUser = (data) => { - { + { return {'value': obj.id, 'label': obj.name} })}/>