| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634 |
- import React, { useState, useEffect, useRef, useContext } from "react";
- import * as Yup from "yup";
- import { Formik, Form, Field } from "formik";
- import { toast } from "react-toastify";
- import { head } from "lodash";
- import { makeStyles } from "@material-ui/core/styles";
- import { green } from "@material-ui/core/colors";
- import Button from "@material-ui/core/Button";
- import IconButton from "@material-ui/core/IconButton";
- import TextField from "@material-ui/core/TextField";
- import Dialog from "@material-ui/core/Dialog";
- import DialogActions from "@material-ui/core/DialogActions";
- import DialogContent from "@material-ui/core/DialogContent";
- import DialogTitle from "@material-ui/core/DialogTitle";
- import CircularProgress from "@material-ui/core/CircularProgress";
- import AttachFileIcon from "@material-ui/icons/AttachFile";
- import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
- import { i18n } from "../../translate/i18n";
- import moment from "moment";
- import api from "../../services/api";
- import toastError from "../../errors/toastError";
- import {
- Box,
- FormControl,
- Grid,
- InputLabel,
- MenuItem,
- Select,
- Tab,
- Tabs,
- } from "@material-ui/core";
- import { AuthContext } from "../../context/Auth/AuthContext";
- import ConfirmationModal from "../ConfirmationModal";
- const useStyles = makeStyles((theme) => ({
- root: {
- display: "flex",
- flexWrap: "wrap",
- backgroundColor: "#fff"
- },
- tabmsg: {
- backgroundColor: theme.palette.campaigntab,
- },
- textField: {
- marginRight: theme.spacing(1),
- flex: 1,
- },
- extraAttr: {
- display: "flex",
- justifyContent: "center",
- alignItems: "center",
- },
- btnWrapper: {
- position: "relative",
- },
- buttonProgress: {
- color: green[500],
- position: "absolute",
- top: "50%",
- left: "50%",
- marginTop: -12,
- marginLeft: -12,
- },
- }));
- const CampaignSchema = Yup.object().shape({
- name: Yup.string()
- .min(2, i18n.t("campaigns.dialog.form.nameShort"))
- .max(50, i18n.t("campaigns.dialog.form.nameLong"))
- .required(i18n.t("campaigns.dialog.form.nameRequired")),
- });
- const CampaignModal = ({
- open,
- onClose,
- campaignId,
- initialValues,
- onSave,
- resetPagination,
- }) => {
- const classes = useStyles();
- const isMounted = useRef(true);
- const { user } = useContext(AuthContext);
- const { companyId } = user;
- const [file, setFile] = useState(null);
- const initialState = {
- name: "",
- message1: "",
- message2: "",
- message3: "",
- message4: "",
- message5: "",
- status: "INATIVA", // INATIVA, PROGRAMADA, EM_ANDAMENTO, CANCELADA, FINALIZADA,
- scheduledAt: "",
- whatsappId: "",
- contactListId: "",
- tagListId: "Nenhuma",
- companyId,
- };
- const [campaign, setCampaign] = useState(initialState);
- const [whatsapps, setWhatsapps] = useState([]);
- const [contactLists, setContactLists] = useState([]);
- const [messageTab, setMessageTab] = useState(0);
- const [attachment, setAttachment] = useState(null);
- const [confirmationOpen, setConfirmationOpen] = useState(false);
- const [campaignEditable, setCampaignEditable] = useState(true);
- const attachmentFile = useRef(null);
- const [tagLists, setTagLists] = useState([]);
- useEffect(() => {
- return () => {
- isMounted.current = false;
- };
- }, []);
- useEffect(() => {
- (async () => {
- try {
- const { data } = await api.get("/files/", {
- params: { companyId }
- });
- setFile(data.files);
- } catch (err) {
- toastError(err);
- }
- })();
- }, []);
- useEffect(() => {
- if (isMounted.current) {
- if (initialValues) {
- setCampaign((prevState) => {
- return { ...prevState, ...initialValues };
- });
- }
- api
- .get(`/contact-lists/list`, { params: { companyId } })
- .then(({ data }) => setContactLists(data));
- api
- .get(`/whatsapp`, { params: { companyId, session: 0 } })
- .then(({ data }) => setWhatsapps(data));
- api.get(`/tags`, { params: { companyId } })
- .then(({ data }) => {
- const fetchedTags = data.tags;
- // Perform any necessary data transformation here
- const formattedTagLists = fetchedTags.map((tag) => ({
- id: tag.id,
- name: tag.name,
- }));
- setTagLists(formattedTagLists);
- })
- .catch((error) => {
- console.error("Error retrieving tags:", error);
- });
-
- if (!campaignId) return;
- api.get(`/campaigns/${campaignId}`).then(({ data }) => {
- setCampaign((prev) => {
- let prevCampaignData = Object.assign({}, prev);
- Object.entries(data).forEach(([key, value]) => {
- if (key === "scheduledAt" && value !== "" && value !== null) {
- prevCampaignData[key] = moment(value).format("YYYY-MM-DDTHH:mm");
- } else {
- prevCampaignData[key] = value === null ? "" : value;
- }
- });
- return {...prevCampaignData, tagListId: data.tagId || "Nenhuma"};
- });
- });
- }
- }, [campaignId, open, initialValues, companyId]);
- useEffect(() => {
- const now = moment();
- const scheduledAt = moment(campaign.scheduledAt);
- const moreThenAnHour =
- !Number.isNaN(scheduledAt.diff(now)) && scheduledAt.diff(now, "hour") > 1;
- const isEditable =
- campaign.status === "INATIVA" ||
- (campaign.status === "PROGRAMADA" && moreThenAnHour);
- setCampaignEditable(isEditable);
- }, [campaign.status, campaign.scheduledAt]);
- const handleClose = () => {
- onClose();
- setCampaign(initialState);
- };
- const handleAttachmentFile = (e) => {
- const file = head(e.target.files);
- if (file) {
- setAttachment(file);
- }
- };
- const handleSaveCampaign = async (values) => {
- try {
- const dataValues = {};
- Object.entries(values).forEach(([key, value]) => {
- if (key === "scheduledAt" && value !== "" && value !== null) {
- dataValues[key] = moment(value).format("YYYY-MM-DD HH:mm:ss");
- } else {
- dataValues[key] = value === "" ? null : value;
- }
- });
- if (campaignId) {
- await api.put(`/campaigns/${campaignId}`, dataValues);
- if (attachment != null) {
- const formData = new FormData();
- formData.append("file", attachment);
- await api.post(`/campaigns/${campaignId}/media-upload`, formData);
- }
- handleClose();
- } else {
- const { data } = await api.post("/campaigns", dataValues);
- if (attachment != null) {
- const formData = new FormData();
- formData.append("file", attachment);
- await api.post(`/campaigns/${data.id}/media-upload`, formData);
- }
- if (onSave) {
- onSave(data);
- }
- handleClose();
- }
- toast.success(i18n.t("campaigns.toasts.success"));
- } catch (err) {
- console.log(err);
- toastError(err);
- }
- };
- const deleteMedia = async () => {
- if (attachment) {
- setAttachment(null);
- attachmentFile.current.value = null;
- }
- if (campaign.mediaPath) {
- await api.delete(`/campaigns/${campaign.id}/media-upload`);
- setCampaign((prev) => ({ ...prev, mediaPath: null, mediaName: null }));
- toast.success(i18n.t("campaigns.toasts.deleted"));
- }
- };
- const renderMessageField = (identifier) => {
- return (
- <Field
- as={TextField}
- id={identifier}
- name={identifier}
- fullWidth
- rows={5}
- label={i18n.t(`campaigns.dialog.form.${identifier}`)}
- placeholder={i18n.t("campaigns.dialog.form.messagePlaceholder")}
- multiline={true}
- variant="outlined"
- helperText={i18n.t("campaigns.dialog.form.helper")}
- disabled={!campaignEditable && campaign.status !== "CANCELADA"}
- />
- );
- };
- const cancelCampaign = async () => {
- try {
- await api.post(`/campaigns/${campaign.id}/cancel`);
- toast.success(i18n.t("campaigns.toasts.cancel"));
- setCampaign((prev) => ({ ...prev, status: "CANCELADA" }));
- resetPagination();
- } catch (err) {
- toast.error(err.message);
- }
- };
- const restartCampaign = async () => {
- try {
- await api.post(`/campaigns/${campaign.id}/restart`);
- toast.success(i18n.t("campaigns.toasts.restart"));
- setCampaign((prev) => ({ ...prev, status: "EM_ANDAMENTO" }));
- resetPagination();
- } catch (err) {
- toast.error(err.message);
- }
- };
- return (
- <div className={classes.root}>
- <ConfirmationModal
- title={i18n.t("campaigns.confirmationModal.deleteTitle")}
- open={confirmationOpen}
- onClose={() => setConfirmationOpen(false)}
- onConfirm={deleteMedia}
- >
- {i18n.t("campaigns.confirmationModal.deleteMessage")}
- </ConfirmationModal>
- <Dialog
- open={open}
- onClose={handleClose}
- fullWidth
- maxWidth="md"
- scroll="paper"
- >
- <DialogTitle id="form-dialog-title">
- {campaignEditable ? (
- <>
- {campaignId
- ? `${i18n.t("campaigns.dialog.update")}`
- : `${i18n.t("campaigns.dialog.new")}`}
- </>
- ) : (
- <>{`${i18n.t("campaigns.dialog.readonly")}`}</>
- )}
- </DialogTitle>
- <div style={{ display: "none" }}>
- <input
- type="file"
- ref={attachmentFile}
- onChange={(e) => handleAttachmentFile(e)}
- />
- </div>
- <Formik
- initialValues={campaign}
- enableReinitialize={true}
- validationSchema={CampaignSchema}
- onSubmit={(values, actions) => {
- setTimeout(() => {
- handleSaveCampaign(values);
- actions.setSubmitting(false);
- }, 400);
- }}
- >
- {({ values, errors, touched, isSubmitting }) => (
- <Form>
- <DialogContent dividers>
- <Grid spacing={2} container>
- <Grid xs={12} md={9} item>
- <Field
- as={TextField}
- label={i18n.t("campaigns.dialog.form.name")}
- name="name"
- error={touched.name && Boolean(errors.name)}
- helperText={touched.name && errors.name}
- variant="outlined"
- margin="dense"
- fullWidth
- className={classes.textField}
- disabled={!campaignEditable}
- />
- </Grid>
- <Grid xs={12} md={4} item>
- <FormControl
- variant="outlined"
- margin="dense"
- fullWidth
- className={classes.formControl}
- >
- <InputLabel id="contactList-selection-label">
- {i18n.t("campaigns.dialog.form.contactList")}
- </InputLabel>
- <Field
- as={Select}
- label={i18n.t("campaigns.dialog.form.contactList")}
- placeholder={i18n.t(
- "campaigns.dialog.form.contactList"
- )}
- labelId="contactList-selection-label"
- id="contactListId"
- name="contactListId"
- error={
- touched.contactListId && Boolean(errors.contactListId)
- }
- disabled={!campaignEditable}
- >
- <MenuItem value="">Nenhuma</MenuItem>
- {contactLists &&
- contactLists.map((contactList) => (
- <MenuItem
- key={contactList.id}
- value={contactList.id}
- >
- {contactList.name}
- </MenuItem>
- ))}
- </Field>
- </FormControl>
- </Grid>
- <Grid xs={12} md={4} item>
- <FormControl
- variant="outlined"
- margin="dense"
- fullWidth
- className={classes.formControl}
- >
- <InputLabel id="tagList-selection-label">
- {i18n.t("campaigns.dialog.form.tagList")}
- </InputLabel>
- <Field
- as={Select}
- label={i18n.t("campaigns.dialog.form.tagList")}
- placeholder={i18n.t("campaigns.dialog.form.tagList")}
- labelId="tagList-selection-label"
- id="tagListId"
- name="tagListId"
- error={touched.tagListId && Boolean(errors.tagListId)}
- disabled={!campaignEditable}
- >
- <MenuItem value="">Nenhuma</MenuItem>
- {Array.isArray(tagLists) &&
- tagLists.map((tagList) => (
- <MenuItem key={tagList.id} value={tagList.id}>
- {tagList.name}
- </MenuItem>
- ))}
- </Field>
- </FormControl>
- </Grid>
- <Grid xs={12} md={4} item>
- <FormControl
- variant="outlined"
- margin="dense"
- fullWidth
- className={classes.formControl}
- >
- <InputLabel id="whatsapp-selection-label">
- {i18n.t("campaigns.dialog.form.whatsapp")}
- </InputLabel>
- <Field
- as={Select}
- label={i18n.t("campaigns.dialog.form.whatsapp")}
- placeholder={i18n.t("campaigns.dialog.form.whatsapp")}
- labelId="whatsapp-selection-label"
- id="whatsappId"
- name="whatsappId"
- error={touched.whatsappId && Boolean(errors.whatsappId)}
- disabled={!campaignEditable}
- >
- <MenuItem value="">Nenhuma</MenuItem>
- {whatsapps &&
- whatsapps.map((whatsapp) => (
- <MenuItem key={whatsapp.id} value={whatsapp.id}>
- {whatsapp.name}
- </MenuItem>
- ))}
- </Field>
- </FormControl>
- </Grid>
- <Grid xs={12} md={4} item>
- <Field
- as={TextField}
- label={i18n.t("campaigns.dialog.form.scheduledAt")}
- name="scheduledAt"
- error={touched.scheduledAt && Boolean(errors.scheduledAt)}
- helperText={touched.scheduledAt && errors.scheduledAt}
- variant="outlined"
- margin="dense"
- type="datetime-local"
- InputLabelProps={{
- shrink: true,
- }}
- fullWidth
- className={classes.textField}
- disabled={!campaignEditable}
- />
- </Grid>
- <Grid xs={12} md={4} item>
- <FormControl
- variant="outlined"
- margin="dense"
- className={classes.FormControl}
- fullWidth
- >
- <InputLabel id="fileListId-selection-label">{i18n.t("campaigns.dialog.form.fileList")}</InputLabel>
- <Field
- as={Select}
- label={i18n.t("campaigns.dialog.form.fileList")}
- name="fileListId"
- id="fileListId"
- placeholder={i18n.t("campaigns.dialog.form.fileList")}
- labelId="fileListId-selection-label"
- value={values.fileListId || ""}
- >
- <MenuItem value={""} >{"Nenhum"}</MenuItem>
- {file.map(f => (
- <MenuItem key={f.id} value={f.id}>
- {f.name}
- </MenuItem>
- ))}
- </Field>
- </FormControl>
- </Grid>
- <Grid xs={12} item>
- <Tabs
- value={messageTab}
- indicatorColor="primary"
- textColor="primary"
- className={classes.tabmsg}
- onChange={(e, v) => setMessageTab(v)}
- variant="fullWidth"
- centered
- style={{
- borderRadius: 2,
- }}
- >
- <Tab label="Msg. 1" index={0} />
- <Tab label="Msg. 2" index={1} />
- <Tab label="Msg. 3" index={2} />
- <Tab label="Msg. 4" index={3} />
- <Tab label="Msg. 5" index={4} />
- </Tabs>
- <Box style={{ paddingTop: 20, border: "none" }}>
- {messageTab === 0 && (
- <>{renderMessageField("message1")}</>
- )}
- {messageTab === 1 && (
- <>{renderMessageField("message2")}</>
- )}
- {messageTab === 2 && (
- <>{renderMessageField("message3")}</>
- )}
- {messageTab === 3 && (
- <>{renderMessageField("message4")}</>
- )}
- {messageTab === 4 && (
- <>{renderMessageField("message5")}</>
- )}
- </Box>
- </Grid>
- {(campaign.mediaPath || attachment) && (
- <Grid xs={12} item>
- <Button startIcon={<AttachFileIcon />}>
- {attachment != null
- ? attachment.name
- : campaign.mediaName}
- </Button>
- {campaignEditable && (
- <IconButton
- onClick={() => setConfirmationOpen(true)}
- color="secondary"
- >
- <DeleteOutlineIcon />
- </IconButton>
- )}
- </Grid>
- )}
- </Grid>
- </DialogContent>
- <DialogActions>
- {campaign.status === "CANCELADA" && (
- <Button
- color="primary"
- onClick={() => restartCampaign()}
- variant="outlined"
- >
- {i18n.t("campaigns.dialog.buttons.restart")}
- </Button>
- )}
- {campaign.status === "EM_ANDAMENTO" && (
- <Button
- color="primary"
- onClick={() => cancelCampaign()}
- variant="outlined"
- >
- {i18n.t("campaigns.dialog.buttons.cancel")}
- </Button>
- )}
- {!attachment && !campaign.mediaPath && campaignEditable && (
- <Button
- color="primary"
- onClick={() => attachmentFile.current.click()}
- disabled={isSubmitting}
- variant="outlined"
- >
- {i18n.t("campaigns.dialog.buttons.attach")}
- </Button>
- )}
- <Button
- onClick={handleClose}
- color="secondary"
- disabled={isSubmitting}
- variant="outlined"
- >
- {i18n.t("campaigns.dialog.buttons.close")}
- </Button>
- {(campaignEditable || campaign.status === "CANCELADA") && (
- <Button
- type="submit"
- color="primary"
- disabled={isSubmitting}
- variant="contained"
- className={classes.btnWrapper}
- >
- {campaignId
- ? `${i18n.t("campaigns.dialog.buttons.edit")}`
- : `${i18n.t("campaigns.dialog.buttons.add")}`}
- {isSubmitting && (
- <CircularProgress
- size={24}
- className={classes.buttonProgress}
- />
- )}
- </Button>
- )}
- </DialogActions>
- </Form>
- )}
- </Formik>
- </Dialog>
- </div>
- );
- };
- export default CampaignModal;
|