index.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. import React, { useState, useEffect, useReducer, useCallback, useContext } from "react";
  2. import { toast } from "react-toastify";
  3. import { useHistory } from "react-router-dom";
  4. import { makeStyles } from "@material-ui/core/styles";
  5. import Paper from "@material-ui/core/Paper";
  6. import Button from "@material-ui/core/Button";
  7. import TextField from "@material-ui/core/TextField";
  8. import InputAdornment from "@material-ui/core/InputAdornment";
  9. import MainContainer from "../../components/MainContainer";
  10. import MainHeader from "../../components/MainHeader";
  11. import Title from "../../components/Title";
  12. import api from "../../services/api";
  13. import { i18n } from "../../translate/i18n";
  14. import MainHeaderButtonsWrapper from "../../components/MainHeaderButtonsWrapper";
  15. import ScheduleModal from "../../components/ScheduleModal";
  16. import ConfirmationModal from "../../components/ConfirmationModal";
  17. import toastError from "../../errors/toastError";
  18. import moment from "moment";
  19. import { SocketContext } from "../../context/Socket/SocketContext";
  20. import { AuthContext } from "../../context/Auth/AuthContext";
  21. import usePlans from "../../hooks/usePlans";
  22. import { Calendar, momentLocalizer } from "react-big-calendar";
  23. import "moment/locale/pt-br";
  24. import "react-big-calendar/lib/css/react-big-calendar.css";
  25. import SearchIcon from "@material-ui/icons/Search";
  26. import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
  27. import EditIcon from "@material-ui/icons/Edit";
  28. import "./Schedules.css"; // Importe o arquivo CSS
  29. import { createMomentLocalizer } from "../../translate/calendar-locale";
  30. // Defina a função getUrlParam antes de usá-la
  31. function getUrlParam(paramName) {
  32. const searchParams = new URLSearchParams(window.location.search);
  33. return searchParams.get(paramName);
  34. }
  35. const eventTitleStyle = {
  36. fontSize: "14px", // Defina um tamanho de fonte menor
  37. overflow: "hidden", // Oculte qualquer conteúdo excedente
  38. whiteSpace: "nowrap", // Evite a quebra de linha do texto
  39. textOverflow: "ellipsis", // Exiba "..." se o texto for muito longo
  40. };
  41. var defaultMessages = {
  42. date: i18n.t("schedules.messages.date"),
  43. time: i18n.t("schedules.messages.time"),
  44. event: i18n.t("schedules.messages.event"),
  45. allDay: i18n.t("schedules.messages.allDay"),
  46. week: i18n.t("schedules.messages.week"),
  47. work_week: i18n.t("schedules.messages.work_week"),
  48. day: i18n.t("schedules.messages.day"),
  49. month: i18n.t("schedules.messages.month"),
  50. previous: i18n.t("schedules.messages.previous"),
  51. next: i18n.t("schedules.messages.next"),
  52. yesterday: i18n.t("schedules.messages.yesterday"),
  53. tomorrow: i18n.t("schedules.messages.tomorrow"),
  54. today: i18n.t("schedules.messages.today"),
  55. agenda: i18n.t("schedules.messages.agenda"),
  56. noEventsInRange: i18n.t("schedules.messages.noEventsInRange"),
  57. showMore: function showMore(total) {
  58. return "+" + total + " " + i18n.t("schedules.messages.showMore");
  59. }
  60. };
  61. const reducer = (state, action) => {
  62. if (action.type === "LOAD_SCHEDULES") {
  63. return [...state, ...action.payload];
  64. }
  65. if (action.type === "UPDATE_SCHEDULES") {
  66. const schedule = action.payload;
  67. const scheduleIndex = state.findIndex((s) => s.id === schedule.id);
  68. if (scheduleIndex !== -1) {
  69. state[scheduleIndex] = schedule;
  70. return [...state];
  71. } else {
  72. return [schedule, ...state];
  73. }
  74. }
  75. if (action.type === "DELETE_SCHEDULE") {
  76. const scheduleId = action.payload;
  77. return state.filter((s) => s.id !== scheduleId);
  78. }
  79. if (action.type === "RESET") {
  80. return [];
  81. }
  82. return state;
  83. };
  84. const useStyles = makeStyles((theme) => ({
  85. mainPaper: {
  86. flex: 1,
  87. padding: theme.spacing(1),
  88. overflowY: "scroll",
  89. ...theme.scrollbarStyles,
  90. },
  91. }));
  92. const Schedules = () => {
  93. const localizer = createMomentLocalizer();
  94. const classes = useStyles();
  95. const history = useHistory();
  96. const { user } = useContext(AuthContext);
  97. const [loading, setLoading] = useState(false);
  98. const [pageNumber, setPageNumber] = useState(1);
  99. const [hasMore, setHasMore] = useState(false);
  100. const [selectedSchedule, setSelectedSchedule] = useState(null);
  101. const [deletingSchedule, setDeletingSchedule] = useState(null);
  102. const [confirmModalOpen, setConfirmModalOpen] = useState(false);
  103. const [searchParam, setSearchParam] = useState("");
  104. const [schedules, dispatch] = useReducer(reducer, []);
  105. const [scheduleModalOpen, setScheduleModalOpen] = useState(false);
  106. const [contactId, setContactId] = useState(+getUrlParam("contactId"));
  107. const fetchSchedules = useCallback(async () => {
  108. try {
  109. const { data } = await api.get("/schedules/", {
  110. params: { searchParam, pageNumber },
  111. });
  112. dispatch({ type: "LOAD_SCHEDULES", payload: data.schedules });
  113. setHasMore(data.hasMore);
  114. setLoading(false);
  115. } catch (err) {
  116. toastError(err);
  117. }
  118. }, [searchParam, pageNumber]);
  119. const handleOpenScheduleModalFromContactId = useCallback(() => {
  120. if (contactId) {
  121. handleOpenScheduleModal();
  122. }
  123. }, [contactId]);
  124. const socketManager = useContext(SocketContext);
  125. useEffect(() => {
  126. dispatch({ type: "RESET" });
  127. setPageNumber(1);
  128. }, [searchParam]);
  129. useEffect(() => {
  130. setLoading(true);
  131. const delayDebounceFn = setTimeout(() => {
  132. fetchSchedules();
  133. }, 500);
  134. return () => clearTimeout(delayDebounceFn);
  135. }, [
  136. searchParam,
  137. pageNumber,
  138. contactId,
  139. fetchSchedules,
  140. handleOpenScheduleModalFromContactId,
  141. ]);
  142. useEffect(() => {
  143. handleOpenScheduleModalFromContactId();
  144. const socket = socketManager.getSocket(user.companyId);
  145. socket.on(`company${user.companyId}-schedule`, (data) => {
  146. if (data.action === "update" || data.action === "create") {
  147. dispatch({ type: "UPDATE_SCHEDULES", payload: data.schedule });
  148. }
  149. if (data.action === "delete") {
  150. dispatch({ type: "DELETE_SCHEDULE", payload: +data.scheduleId });
  151. }
  152. });
  153. return () => {
  154. socket.disconnect();
  155. };
  156. }, [handleOpenScheduleModalFromContactId, socketManager, user]);
  157. const cleanContact = () => {
  158. setContactId("");
  159. };
  160. const handleOpenScheduleModal = () => {
  161. setSelectedSchedule(null);
  162. setScheduleModalOpen(true);
  163. };
  164. const handleCloseScheduleModal = () => {
  165. setSelectedSchedule(null);
  166. setScheduleModalOpen(false);
  167. };
  168. const handleSearch = (event) => {
  169. setSearchParam(event.target.value.toLowerCase());
  170. };
  171. const handleEditSchedule = (schedule) => {
  172. setSelectedSchedule(schedule);
  173. setScheduleModalOpen(true);
  174. };
  175. const handleDeleteSchedule = async (scheduleId) => {
  176. try {
  177. await api.delete(`/schedules/${scheduleId}`);
  178. toast.success(i18n.t("schedules.toasts.deleted"));
  179. } catch (err) {
  180. toastError(err);
  181. }
  182. setDeletingSchedule(null);
  183. setSearchParam("");
  184. setPageNumber(1);
  185. dispatch({ type: "RESET" });
  186. setPageNumber(1);
  187. await fetchSchedules();
  188. };
  189. const loadMore = () => {
  190. setPageNumber((prevState) => prevState + 1);
  191. };
  192. const handleScroll = (e) => {
  193. if (!hasMore || loading) return;
  194. const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
  195. if (scrollHeight - (scrollTop + 100) < clientHeight) {
  196. loadMore();
  197. }
  198. };
  199. const truncate = (str, len) => {
  200. if (str.length > len) {
  201. return str.substring(0, len) + "...";
  202. }
  203. return str;
  204. };
  205. return (
  206. <MainContainer>
  207. <ConfirmationModal
  208. title={
  209. deletingSchedule &&
  210. `${i18n.t("schedules.confirmationModal.deleteTitle")}`
  211. }
  212. open={confirmModalOpen}
  213. onClose={() => setConfirmModalOpen(false)}
  214. onConfirm={() => handleDeleteSchedule(deletingSchedule.id)}
  215. >
  216. {i18n.t("schedules.confirmationModal.deleteMessage")}
  217. </ConfirmationModal>
  218. <ScheduleModal
  219. open={scheduleModalOpen}
  220. onClose={handleCloseScheduleModal}
  221. reload={fetchSchedules}
  222. aria-labelledby="form-dialog-title"
  223. scheduleId={selectedSchedule && selectedSchedule.id}
  224. contactId={contactId}
  225. cleanContact={cleanContact}
  226. />
  227. <MainHeader>
  228. <Title>{i18n.t("schedules.title")} ({schedules.length})</Title>
  229. <MainHeaderButtonsWrapper>
  230. <TextField
  231. placeholder={i18n.t("contacts.searchPlaceholder")}
  232. type="search"
  233. value={searchParam}
  234. onChange={handleSearch}
  235. InputProps={{
  236. startAdornment: (
  237. <InputAdornment position="start">
  238. <SearchIcon style={{ color: "gray" }} />
  239. </InputAdornment>
  240. ),
  241. }}
  242. />
  243. <Button
  244. variant="contained"
  245. color="primary"
  246. onClick={handleOpenScheduleModal}
  247. >
  248. {i18n.t("schedules.buttons.add")}
  249. </Button>
  250. </MainHeaderButtonsWrapper>
  251. </MainHeader>
  252. <Paper className={classes.mainPaper} variant="outlined" onScroll={handleScroll}>
  253. <Calendar
  254. messages={defaultMessages}
  255. formats={{
  256. agendaDateFormat: "DD/MM ddd",
  257. weekdayFormat: "dddd"
  258. }}
  259. localizer={localizer}
  260. events={schedules.map((schedule) => ({
  261. title: (
  262. <div className="event-container">
  263. <div style={eventTitleStyle}>{schedule.contact.name}</div>
  264. <DeleteOutlineIcon
  265. onClick={() => handleDeleteSchedule(schedule.id)}
  266. className="delete-icon"
  267. />
  268. <EditIcon
  269. onClick={() => {
  270. handleEditSchedule(schedule);
  271. setScheduleModalOpen(true);
  272. }}
  273. className="edit-icon"
  274. />
  275. </div>
  276. ),
  277. start: new Date(schedule.sendAt),
  278. end: new Date(schedule.sendAt),
  279. }))}
  280. startAccessor="start"
  281. endAccessor="end"
  282. style={{ height: 500 }}
  283. />
  284. </Paper>
  285. </MainContainer>
  286. );
  287. };
  288. export default Schedules;