%PDF- <> %âãÏÓ endobj 2 0 obj <> endobj 3 0 obj <>/ExtGState<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI] >>/Annots[ 28 0 R 29 0 R] /MediaBox[ 0 0 595.5 842.25] /Contents 4 0 R/Group<>/Tabs/S>> endobj ºaâÚÎΞ-ÌE1ÍØÄ÷{òò2ÿ ÛÖ^ÔÀá TÎ{¦?§®¥kuµùÕ5sLOšuY>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<> endobj 2 0 obj<>endobj 2 0 obj<>es 3 0 R>> endobj 2 0 obj<> ox[ 0.000000 0.000000 609.600000 935.600000]/Fi endobj 3 0 obj<> endobj 7 1 obj<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]>>/Subtype/Form>> stream
/* eslint-disable react-hooks/exhaustive-deps */ import React, { useEffect, useState, useRef } from "react"; import AuthService from "../../services/AuthService"; import Service from "../../services/Service"; import FileService from "../../services/FileService"; import AddedLeads from "./AddedLeads"; import { AntdSelect, Tinymce, GetTinymceContent } from "../../components"; import util from "../../util"; import { Result, Row, Col, Button, Input, Card, Table, Tabs, message, Modal, Drawer, Typography, Tag, Spin, } from "antd"; import { ExclamationCircleOutlined, PlusOutlined, CloseOutlined, CheckCircleOutlined, } from "@ant-design/icons"; const { Text } = Typography; const $ = window.$; function CampaignsList({ refOb, type, openEditForm, masters }) { const [loading, setLoading] = useState(false); const [result, setResult] = useState([]); const [paging, setPaging] = useState({ p: 1, ps: 10 }); const [rowDtl, setRowDtl] = useState(null); const filterParamsRef = useRef({}); const wh = $(window).height(); const cols = [ { title: "Campaign Name", dataIndex: "name" }, { title: "Template", dataIndex: "template" }, { title: "Sender", render: (row) => ( <div> <div>{row.sender_name}</div> <div className="fs11 text-secondary">{row.sender_email}</div> {!!row.reply_to && ( <div className="pt3 fs11 text-secondary"> Reply To: <strong>{row.reply_to}</strong> </div> )} </div> ), hidden: type !== "Email", }, { title: "No. of Leads", dataIndex: "leads_count", render: (leads_count, row) => ( <div className="link w80 text-center" style={{ border: "1px solid #1890ff", borderRadius: "16px" }} onClick={() => { setRowDtl(row); }} > {leads_count} </div> ), }, { title: "Status", dataIndex: "status", render: (status) => ( <div> {status === "Draft" ? ( <Text type="warning" strong> {status} </Text> ) : ( <Text type="success" strong> <CheckCircleOutlined /> {status} </Text> )} </div> ), }, { title: "Created", dataIndex: "created", width: 100, render: (dt) => <div>{util.getDate(dt, "DD MMM YYYY")}</div>, }, { title: "", dataIndex: "id", width: "86px", render: (id, row) => ( <div className="text-center"> <Button.Group size="small"> <Button onClick={() => openEditForm(row)}> <i className="fa fa-edit"></i> </Button> <Button onClick={() => deleteRecord(id)} disabled={row.status === "Published"} > <i className="fa fa-times-circle text-danger"></i> </Button> </Button.Group> </div> ), show: true, }, ].filter((v) => !v.hidden); const closeDrawer = () => { setRowDtl(null); getList(filterParamsRef.current, paging.p); }; const getList = (params = {}, p = 1) => { filterParamsRef.current = params; setLoading(true); Service.socialCampaigns({ ...params, type }) .then(({ data }) => { data.result.forEach((v) => { v.key = v.id; }); setResult(data.result); setPaging({ ...paging, p }); }) .catch((e) => { message.error(e.message); }) .finally(() => { setLoading(false); }); }; const deleteRecord = (id) => { message.destroy(); const fn = () => { setLoading(true); Service.deleteSocialCampaign(id) .then(({ data }) => { message.success(data.message || "Deleted"); getList(); }) .catch((e) => { message.error(e.message); }) .finally(() => { setLoading(false); }); }; Modal.confirm({ title: `Do you want to delete this campaign?`, icon: <ExclamationCircleOutlined />, content: "", okText: "Yes", okType: "danger", cancelText: "No", onOk() { fn(); }, onCancel() {}, }); }; refOb.current = { getList, }; useEffect(() => { getList(); }, []); return ( <div className="def-pag-custom"> <Table size="small" dataSource={result} columns={cols} loading={loading} scroll={{ y: wh - 220 }} pagination={{ showTotal: (total, range) => `${range[0]}-${range[1]} of ${total} items`, showSizeChanger: true, pageSize: paging.ps, current: paging.p, onChange: (p, ps) => { setPaging({ p, ps }); }, }} /> <Drawer title={ <div> Sent Leads To Campaign -{" "} <span className="text-secondary">{rowDtl?.name}</span> </div> } open={rowDtl !== null} placement="right" width="95%" destroyOnClose closable={false} onClose={closeDrawer} maskClosable={false} extra={ <div className="cpointer" onClick={closeDrawer}> <CloseOutlined /> </div> } > <AddedLeads type={type} campaignDtl={rowDtl} masters={masters} closeDrawer={closeDrawer} /> </Drawer> </div> ); } function CampaignFormModal({ refOb, type, templates = [], callback }) { const [showModal, setShowModal] = useState(false); const [saving, setSaving] = useState(false); const [loading, setLoading] = useState(false); const [fd, setFd] = useState({ test_emails: "" }); const handleChange = (v, k) => { setFd({ ...fd, [k]: v }); }; const testCampaignWithTestEmails = async () => { const emails = fd.test_emails.split(",").map((email) => email.trim()); setLoading(true); try { const { data: { result }, } = await Service.leadsN({ emails, social: 1 }); if (result && result.length > 0) { const testFd = { campaign_id: fd.id, leads: [], filters: {} }; testFd.leads = result.map((v) => ({ app_id: v.id, mob: "+91" + v.mob, email: v.email, name: v.name, state: v.state, program: v.program, plan: v.plan, system_id: v.application_no, lpage_id: v.lpage_id, })); await Service.testLeadsToSocialCampaign(testFd); message.success("Test email sent."); } } catch (e) { console.log(e); } setLoading(false); }; const handleOk = (testing = "") => { message.destroy(); fd.type = type; setSaving(true); Service.saveSocialCampaign(fd) .then(({ data }) => { message.success(data.message || "Saved"); if (testing === "T") { fd.id = data.id; testCampaignWithTestEmails(); } else { setShowModal(false); } callback(); }) .catch((e) => { message.error(e.message); }) .finally(() => { setSaving(false); }); }; const handleCancel = () => { setShowModal(false); }; refOb.current = { open: (dtl) => { const d = dtl ? { ...dtl } : { status: "Draft", test_emails: "" }; setFd(d); setShowModal(true); }, }; return ( <Modal title={`${fd.id ? "Edit" : "Create"} ${type} Campaign`} open={showModal} okText="Save" onOk={handleOk} okButtonProps={{ loading: saving }} onCancel={handleCancel} cancelText="Close" destroyOnClose maskClosable={false} width={1100} style={{ top: 20 }} > <Spin spinning={loading}> <Row gutter={[16, 16]}> <Col span="12"> <label className="req">Campaign Name</label> <Input value={fd.name || ""} onChange={(e) => handleChange(e.target.value, "name")} /> </Col> <Col span="12"> <label className="req">Template</label> <AntdSelect showSearch allowClear sort placeholder="Status (All)" options={templates.map((v) => ({ id: v.id, name: v.name }))} value={fd.template_id} onChange={(v) => { handleChange(v, "template_id"); }} /> </Col> {type === "Email" && ( <> <Col span="8"> <label className="">Sender Name</label> <Input value={fd.sender_name || ""} onChange={(e) => handleChange(e.target.value, "sender_name")} disabled={fd.status === "Published"} /> </Col> <Col span="8"> <label className="">Sender Email</label> <Input value={fd.sender_email || ""} onChange={(e) => handleChange(e.target.value, "sender_email")} disabled={fd.status === "Published"} /> </Col> <Col span="8"> <label className="">Reply To</label> <Input value={fd.reply_to || ""} onChange={(e) => handleChange(e.target.value, "reply_to")} disabled={fd.status === "Published"} /> </Col> <Col span="24"> <div className="d-flex"> <div style={{ width: "100%", padding: "0 10px 0 0" }}> <Input placeholder="Enter lead emails" value={fd.test_emails} onChange={(e) => handleChange(e.target.value, "test_emails") } /> </div> <div> <Button onClick={() => handleOk("T")}>Test</Button> </div> </div> </Col> </> )} </Row> </Spin> </Modal> ); } function Campaigns({ type, masters }) { const [loading, setLoading] = useState(false); const [templates, setTemplates] = useState([]); const [filterFd, setFilterFd] = useState({ k: "" }); const listRef = useRef({}); const formRef = useRef({}); const getTemplates = () => { setLoading(true); Service.socialTemplates({ status: 1, type }) .then(({ data }) => { setTemplates(data.result); }) .catch((e) => { message.error(e.message); }) .finally(() => { setLoading(false); }); }; useEffect(() => { getTemplates(); }, []); return ( <Spin spinning={loading}> <div className="d-flex" style={{ gap: "16px", flexDirection: "column" }}> <Card size="small" bodyStyle={{ padding: 0 }}> <div className="above-tbl-filter-pad"> <Row align="middle" justify="space-between"> <Row gutter={[5, 5]}> <Col> <Input placeholder="Enter text..." allowClear value={filterFd.k} onChange={(e) => setFilterFd({ ...filterFd, k: e.target.value }) } /> </Col> <Col className="w150"> <AntdSelect showSearch allowClear placeholder="Status (All)" options={["Draft", "Published"]} value={filterFd.status} onChange={(v) => { setFilterFd({ ...filterFd, status: v }); }} /> </Col> <Col> <Button type="primary" onClick={() => listRef.current.getList(filterFd)} > Search </Button> </Col> </Row> <div> <Button type="primary" onClick={() => formRef.current.open()}> <PlusOutlined /> Create New </Button> </div> </Row> </div> <div className="bdr-top"> <CampaignsList refOb={listRef} openEditForm={(dtl) => formRef.current.open(dtl)} type={type} masters={masters} /> </div> </Card> <CampaignFormModal refOb={formRef} type={type} templates={templates} callback={() => listRef.current.getList()} /> </div> </Spin> ); } function TemplatesList({ refOb, type, openEditForm }) { const [loading, setLoading] = useState(false); const [result, setResult] = useState([]); const [paging, setPaging] = useState({ p: 1, ps: 10 }); const filterParamsRef = useRef({}); const wh = $(window).height(); const cols = [ { title: "Template Name", render: (row) => ( <div className="d-flex align-items-center" style={{ gap: "16px" }}> <div className="uc bold600">{row.name}</div> {!!row.media_file_url && ( <a href={row.media_file_url} target="_blank" rel="noreferrer" className="fs11" style={{ lineHeight: "11px" }} > VIEW MEDIA FILE </a> )} </div> ), }, { title: "Status", dataIndex: "status", width: 150, render: (status) => ( <div> {status * 1 === 1 ? ( <Text type="success" strong> Active </Text> ) : ( <Text type="danger" strong> Inactive </Text> )} </div> ), }, { title: "", dataIndex: "id", width: "86px", render: (id, row) => ( <div className="text-center"> <Button.Group size="small"> <Button onClick={() => openEditForm(row)}> <i className="fa fa-edit"></i> </Button> <Button onClick={() => deleteRecord(id)}> <i className="fa fa-times-circle text-danger"></i> </Button> </Button.Group> </div> ), show: true, }, ]; const getList = (params = {}, p = 1) => { filterParamsRef.current = params; setLoading(true); Service.socialTemplates({ ...params, type }) .then(({ data }) => { data.result.forEach((v) => { v.key = v.id; }); setResult(data.result); setPaging({ ...paging, p }); }) .catch((e) => { message.error(e.message); }) .finally(() => { setLoading(false); }); }; const deleteRecord = (id) => { message.destroy(); const fn = () => { setLoading(true); Service.deleteSocialTemplate(id) .then(({ data }) => { message.success(data.message || "Deleted"); getList(); }) .catch((e) => { message.error(e.message); }) .finally(() => { setLoading(false); }); }; Modal.confirm({ title: `Do you want to delete this template?`, icon: <ExclamationCircleOutlined />, content: "", okText: "Yes", okType: "danger", cancelText: "No", onOk() { fn(); }, onCancel() {}, }); }; refOb.current = { getList, }; useEffect(() => { getList(); }, []); return ( <div className="def-pag-custom"> <Table size="small" dataSource={result} columns={cols} loading={loading} scroll={{ y: wh - 220 }} pagination={{ showTotal: (total, range) => `${range[0]}-${range[1]} of ${total} items`, showSizeChanger: true, pageSize: paging.ps, current: paging.p, onChange: (p, ps) => { setPaging({ p, ps }); }, }} /> </div> ); } function TemplateFormModal({ refOb, type, callback }) { const [showModal, setShowModal] = useState(false); const [saving, setSaving] = useState(false); const [fd, setFd] = useState({}); const tags = [ "NAME", "EMAIL", "MOBILE_NO", "STATE", "LOGIN_URL", "SYSTEM_ID", "PROGRAM_NAME", "PLAN_NAME", ]; const handleChange = (v, k) => { setFd({ ...fd, [k]: v }); }; const insertTag = (tag, ta, inputType) => { if (inputType === "subject" || type !== "Email") { const start = ta.selectionStart; const end = ta.selectionEnd; var finText = ta.value.substring(0, start) + tag + ta.value.substring(end); ta.value = finText; ta.focus({ preventScroll: true }); ta.selectionEnd = start + tag.length; } else { window.tinymce.EditorManager.get("taBody").execCommand( "mceInsertContent", false, tag ); } if (type === "Email") { setFd({ ...fd, subject: document.getElementById("taSubject").value, }); } else { setFd({ ...fd, body: document.getElementById("taBody").value, }); } }; const handleOk = () => { message.destroy(); fd.type = type; if (type === "Email") { fd.body = GetTinymceContent("taBody"); } setSaving(true); Service.saveSocialTemplate(fd) .then(({ data }) => { message.success(data.message || "Saved"); callback(); setShowModal(false); }) .catch((e) => { message.error(e.message); }) .finally(() => { setSaving(false); }); }; const handleCancel = () => { setShowModal(false); }; const uploadMedia = async (e) => { //const filename = e.target.value; util.showLoader(); try { let rs = await FileService.upload(e.target.files[0], 600); setFd({ ...fd, media_file_id: rs.data.file_id, media_file_url: rs.data.file_url, is_media_image: rs.data.is_image, is_media_pdf: rs.data.is_pdf, }); } catch (e) {} e.target.value = ""; util.hideLoader(); }; refOb.current = { open: (dtl) => { const d = dtl ? { ...dtl } : { status: "1" }; setFd(d); setShowModal(true); }, }; return ( <Modal title={`${fd.id ? "Edit" : "Create"} ${type} template`} open={showModal} okText="Save" onOk={handleOk} okButtonProps={{ loading: saving }} onCancel={handleCancel} cancelText="Close" destroyOnClose maskClosable={false} width={1100} style={{ top: 20 }} > <Row gutter={[16, 16]}> <Col span="24"> <label className="req">Template Name</label> <Input value={fd.name || ""} onChange={(e) => handleChange(e.target.value, "name")} /> </Col> {type === "Email" && ( <Col span="24"> <label className="req">Subject</label> <Row gutter={[8, 8]} className="mb10"> {tags.map((tag) => ( <Col key={tag}> <Tag color="purple" className="cpointer noselect" onClick={() => insertTag( `%${tag}%`, document.getElementById("taSubject"), "subject" ) } > {tag} </Tag> </Col> ))} </Row> <Input id="taSubject" value={fd.subject || ""} onChange={(e) => handleChange(e.target.value, "subject")} /> </Col> )} <Col span="24"> <label className="req">Body</label> <Row gutter={[8, 8]} className="mb10"> {tags.map((tag) => ( <Col key={tag}> <Tag color="purple" className="cpointer noselect" onClick={() => insertTag( `%${tag}%`, document.getElementById("taBody"), "body" ) } > {tag} </Tag> </Col> ))} </Row> {type === "Email" ? ( <Tinymce id="taBody" data={fd.body || ""} height="400" fullpage /> ) : ( <Input.TextArea id="taBody" rows="5" value={fd.body || ""} onChange={(e) => handleChange(e.target.value, "body")} /> )} </Col> {type === "Whatsapp" && ( <Col span="24"> <label>Media File (Photo, Video or Pdf)</label> <div> <label className="ant-btn m0"> <input type="file" className="d-none" onChange={(e) => uploadMedia(e)} /> <i className="fa fa-upload"></i> Upload Media </label> {!!fd.media_file_url && ( <div className="pt5 d-flex align-items-center" style={{ gap: "10px" }} > <a href={fd.media_file_url} target="_blank" rel="noreferrer"> VIEW MEDIA FILE </a> <div className="cpointer text-danger" onClick={() => { setFd({ ...fd, media_file_id: null, media_file_url: "", }); }} > REMOVE MEDIA </div> </div> )} </div> </Col> )} <Col span="24"> <label className="req">Status</label> <AntdSelect options={[ { value: "1", label: "Active" }, { value: "0", label: "Inactive" }, ]} value={fd.status} onChange={(v) => { handleChange(v, "status"); }} /> </Col> </Row> </Modal> ); } function Templates({ type }) { const [filterFd, setFilterFd] = useState({ k: "" }); const listRef = useRef({}); const formRef = useRef({}); return ( <div className="d-flex" style={{ gap: "16px", flexDirection: "column" }}> <Card size="small" bodyStyle={{ padding: 0 }}> <div className="above-tbl-filter-pad"> <Row align="middle" justify="space-between"> <Row gutter={[5, 5]}> <Col> <Input placeholder="Enter text..." allowClear value={filterFd.k} onChange={(e) => setFilterFd({ ...filterFd, k: e.target.value }) } /> </Col> <Col className="w150"> <AntdSelect showSearch allowClear placeholder="Status (All)" options={[ { value: "1", label: "Active" }, { value: "0", label: "Inactive" }, ]} value={filterFd.status} onChange={(v) => { setFilterFd({ ...filterFd, status: v }); }} /> </Col> <Col> <Button type="primary" onClick={() => listRef.current.getList(filterFd)} > Search </Button> </Col> </Row> <div> <Button type="primary" onClick={() => formRef.current.open()}> <PlusOutlined /> Create New </Button> </div> </Row> </div> <div className="bdr-top"> <TemplatesList refOb={listRef} openEditForm={(dtl) => formRef.current.open(dtl)} type={type} /> </div> </Card> <TemplateFormModal refOb={formRef} type={type} callback={() => listRef.current.getList()} /> </div> ); } export default function SocialCampaigns() { const modules = AuthService.getModules(); const [masters, setMasters] = useState({ states: [], programs: [], utm_groups: [], superbot_dispositions: [], acs: [], disciplines: [], remarks: [], schools: [], lpages: [], }); useEffect(() => { Service.mastersForLeads().then(({ data }) => setMasters(data.result)); }, []); if (!modules.social_campaign) { return ( <Result status="403" title="403" subTitle="Sorry, you are not authorized to access this page." /> ); } return ( <div> <div className="page-heading">Social Campaigns</div> <Tabs destroyInactiveTabPane items={[ { label: "Email Campaigns", key: "1", children: <Campaigns type="Email" masters={masters} />, }, { label: "SMS Campaigns", key: "2", children: <Campaigns type="SMS" masters={masters} />, }, { label: "Whatsapp Campaigns", key: "3", children: <Campaigns type="Whatsapp" masters={masters} />, }, { label: "Email Templates", key: "4", children: <Templates type="Email" />, }, { label: "SMS Templates", key: "5", children: <Templates type="SMS" />, }, { label: "Whatsapp Templates", key: "6", children: <Templates type="Whatsapp" />, }, ]} /> </div> ); }