diff --git a/src/components/EmailDeliveryReport/EmailDeliveryReport.tsx b/src/components/EmailDeliveryReport/EmailDeliveryReport.tsx index 817bb6a7..cebe47b5 100644 --- a/src/components/EmailDeliveryReport/EmailDeliveryReport.tsx +++ b/src/components/EmailDeliveryReport/EmailDeliveryReport.tsx @@ -1,8 +1,10 @@ -import { Tag, Button, Row, Col } from "antd"; -import { useState, Key } from "react"; +import { Tag, Button, Row, Col, message } from "antd"; +import { useState, Key, useEffect } from "react"; import { StyledTable, StyledTag } from "./EmailDeliveryReport.styled"; import { ColumnType } from "antd/es/table"; +import { ClearOutlined, ReloadOutlined } from "@ant-design/icons"; +import { getEmailDeliveryReportEnumerator } from "../../redux/emails/apiService"; interface EnumeratorStatus { enumerator_id: string; @@ -14,93 +16,205 @@ interface EnumeratorStatus { supervisor_email: string; } -const columns: ColumnType[] = [ - { - title: "Enumerator ID", - dataIndex: "enumerator_id", - key: "enumerator_id", - sorter: (a, b) => a.enumerator_id.localeCompare(b.enumerator_id), - }, - { - title: "Enumerator Name", - dataIndex: "enumerator_name", - key: "enumerator_name", - sorter: (a, b) => a.enumerator_name.localeCompare(b.enumerator_name), - }, - { - title: "Enumerator Email", - dataIndex: "enumerator_email", - key: "enumerator_email", - sorter: (a, b) => a.enumerator_email.localeCompare(b.enumerator_email), - }, - { - title: "Status", - dataIndex: "status", - key: "status", - filters: [ - { text: "Sent", value: "sent" }, - { text: "Failed", value: "failed" }, - ], - onFilter: (value: boolean | Key, record: EnumeratorStatus) => - typeof value === "boolean" - ? false - : record.status.includes(value.toString()), - sorter: (a, b) => a.status.localeCompare(b.status), - render: (status: string) => { - let color = "green"; - if (status === "failed") { - color = "red"; - } - return ( - - {status.toUpperCase()} - - ); - }, - }, - { - title: "Error", - dataIndex: "error_message", - key: "error_message", - sorter: (a, b) => a.error_message.localeCompare(b.error_message), - }, - { - title: "Supervisor Name", - dataIndex: "supervisor_name", - key: "supervisor_name", - sorter: (a, b) => a.supervisor_name.localeCompare(b.supervisor_name), - }, - { - title: "Supervisor Email", - dataIndex: "supervisor_email", - key: "supervisor_email", - sorter: (a, b) => a.supervisor_email.localeCompare(b.supervisor_email), - }, -]; - -const formatDateTime = (date: string) => { - return new Date(date).toLocaleString(); -}; - -const combineSlotDateTime = (slot_date: string, slot_time: string) => { - const cleanedSlotDate = new Date(slot_date).toDateString(); - const combinedDateTime = new Date( - `${cleanedSlotDate} ${slot_time}` - ).toLocaleString(); - return combinedDateTime; -}; - function EmailDeliveryReport({ deliveryReportData, slot_type, + email_config_uid, }: { deliveryReportData: any; slot_type: string; + email_config_uid?: string; }) { const [selectedReportUID, setSelectedReportUID] = useState( deliveryReportData[0].email_delivery_report_uid ); const [tableKey, setTableKey] = useState(0); + const [enumeratorData, setEnumeratorData] = useState([]); + const [loading, setLoading] = useState(false); + + const selectedReport = deliveryReportData.find( + (report: any) => report.email_delivery_report_uid === selectedReportUID + ); + + // Function to fetch enumerator data for selected report + const fetchEnumeratorData = async (reportUID: string) => { + if (!reportUID) return; + + const configUID = email_config_uid || selectedReport?.email_config_uid; + if (!configUID) return; + + setLoading(true); + try { + const response = await getEmailDeliveryReportEnumerator( + configUID, + reportUID + ); + + if ((response as any)?.data?.success) { + const responseData = (response as any).data.data; + // Find the matching delivery report and extract enumerator_status + const matchingReport = responseData.find( + (report: any) => + report.email_delivery_report_uid.toString() == reportUID + ); + setEnumeratorData(matchingReport?.enumerator_status || []); + } else { + setEnumeratorData([]); + message.error("Failed to fetch enumerator data"); + } + } catch (error) { + console.error("Error fetching enumerator data:", error); + setEnumeratorData([]); + message.error("Error occurred while fetching enumerator data"); + } finally { + setLoading(false); + } + }; + + // Effect to fetch enumerator data when selectedReportUID changes + useEffect(() => { + if (selectedReportUID) { + fetchEnumeratorData(selectedReportUID); + } + }, [selectedReportUID, selectedReport]); + useEffect(() => { + if (deliveryReportData.length > 0 && selectedReportUID) { + fetchEnumeratorData(selectedReportUID); + } + }, []); + + const columns: ColumnType[] = [ + { + title: "Enumerator ID", + dataIndex: "enumerator_id", + key: "enumerator_id", + sorter: (a, b) => a.enumerator_id.localeCompare(b.enumerator_id), + filters: Array.from( + new Set( + enumeratorData.map( + (status: EnumeratorStatus) => status.enumerator_id + ) || [] + ) + ).map((id) => ({ text: String(id), value: String(id) })), + onFilter: (value: boolean | Key, record: EnumeratorStatus) => + record.enumerator_id === value, + }, + { + title: "Enumerator Name", + dataIndex: "enumerator_name", + key: "enumerator_name", + sorter: (a, b) => a.enumerator_name.localeCompare(b.enumerator_name), + filters: Array.from( + new Set( + enumeratorData.map( + (status: EnumeratorStatus) => status.enumerator_name + ) || [] + ) + ).map((name) => ({ text: String(name), value: String(name) })), + onFilter: (value: boolean | Key, record: EnumeratorStatus) => + record.enumerator_name === value, + }, + { + title: "Enumerator Email", + dataIndex: "enumerator_email", + key: "enumerator_email", + sorter: (a, b) => a.enumerator_email.localeCompare(b.enumerator_email), + filters: Array.from( + new Set( + enumeratorData.map( + (status: EnumeratorStatus) => status.enumerator_email + ) || [] + ) + ).map((email) => ({ text: String(email), value: String(email) })), + onFilter: (value: boolean | Key, record: EnumeratorStatus) => + record.enumerator_email === value, + }, + { + title: "Status", + dataIndex: "status", + key: "status", + filters: [ + { text: "Sent", value: "sent" }, + { text: "Failed", value: "failed" }, + ], + onFilter: (value: boolean | Key, record: EnumeratorStatus) => + typeof value === "boolean" + ? false + : record.status.includes(value.toString()), + sorter: (a, b) => a.status.localeCompare(b.status), + render: (status: string) => { + let color = "green"; + if (status === "failed") { + color = "red"; + } + return ( + + {status.toUpperCase()} + + ); + }, + }, + { + title: "Error", + dataIndex: "error_message", + key: "error_message", + filters: Array.from( + new Set( + enumeratorData.map( + (status: EnumeratorStatus) => status.error_message || "No Error" + ) || [] + ) + ).map((error) => ({ text: String(error), value: String(error) })), + onFilter: (value: boolean | Key, record: EnumeratorStatus) => + (record.error_message || "No Error") === value, + sorter: (a, b) => + (a.error_message || "").localeCompare(b.error_message || ""), + }, + { + title: "Supervisor Name", + dataIndex: "supervisor_name", + key: "supervisor_name", + sorter: (a, b) => a.supervisor_name.localeCompare(b.supervisor_name), + filters: Array.from( + new Set( + enumeratorData.map( + (status: EnumeratorStatus) => status.supervisor_name + ) || [] + ) + ).map((name) => ({ text: String(name), value: String(name) })), + onFilter: (value: boolean | Key, record: EnumeratorStatus) => + record.supervisor_name === value, + }, + { + title: "Supervisor Email", + dataIndex: "supervisor_email", + key: "supervisor_email", + sorter: (a, b) => a.supervisor_email.localeCompare(b.supervisor_email), + filters: Array.from( + new Set( + enumeratorData.map( + (status: EnumeratorStatus) => status.supervisor_email + ) || [] + ) + ).map((email) => ({ text: String(email), value: String(email) })), + onFilter: (value: boolean | Key, record: EnumeratorStatus) => + record.supervisor_email === value, + }, + ]; + + const formatDateTime = (date: string) => { + return new Date(date).toLocaleString(); + }; + + const combineSlotDateTime = (slot_date: string, slot_time: string) => { + const cleanedSlotDate = new Date(slot_date).toDateString(); + const combinedDateTime = new Date( + `${cleanedSlotDate} ${slot_time}` + ).toLocaleString(); + return combinedDateTime; + }; + + const tableData = enumeratorData || []; const resetTable = () => { setTableKey((prevKey) => prevKey + 1); @@ -165,52 +279,37 @@ function EmailDeliveryReport({ - Total Enumerators with emails Sent:{" "} + Emails Sent to Enumerators:{" "} { - deliveryReportData - .find( - (report: any) => - report.email_delivery_report_uid == selectedReportUID - ) - ?.enumerator_status.filter( - (status: EnumeratorStatus) => status.status === "sent" - ).length + enumeratorData.filter( + (status: EnumeratorStatus) => status.status === "sent" + ).length } - Total Enumerators with emails Failed:{" "} + Emails Failed for Enumerators:{" "} { - deliveryReportData - .find( - (report: any) => - report.email_delivery_report_uid == selectedReportUID - ) - ?.enumerator_status.filter( - (status: EnumeratorStatus) => status.status === "failed" - ).length + enumeratorData.filter( + (status: EnumeratorStatus) => status.status === "failed" + ).length } - - + + diff --git a/src/modules/Emails/ConfigureEmails/EmailConfigForm.tsx b/src/modules/Emails/ConfigureEmails/EmailConfigForm.tsx index 25613a0b..b6a7944b 100644 --- a/src/modules/Emails/ConfigureEmails/EmailConfigForm.tsx +++ b/src/modules/Emails/ConfigureEmails/EmailConfigForm.tsx @@ -11,6 +11,7 @@ import { useNavigate, useParams } from "react-router-dom"; import { getAllUsers } from "../../../redux/userManagement/userManagementActions"; import FullScreenLoader from "../../../components/Loaders/FullScreenLoader"; import { set } from "lodash"; +import { CustomBtn } from "../../../shared/Global.styled"; const { Option } = Select; @@ -527,19 +528,16 @@ const EmailConfigForm = ({ handleContinue, configNames, sctoForms }: any) => { )} )} - + ); }; diff --git a/src/modules/Emails/ConfigureEmails/EmailScheduleForm.tsx b/src/modules/Emails/ConfigureEmails/EmailScheduleForm.tsx index c2beb9e3..cc68933f 100644 --- a/src/modules/Emails/ConfigureEmails/EmailScheduleForm.tsx +++ b/src/modules/Emails/ConfigureEmails/EmailScheduleForm.tsx @@ -19,6 +19,7 @@ import dayjs from "dayjs"; import EmailScheduleFilter from "../../../components/EmailScheduleFilter"; import EmailScheduleFilterCard from "../../../components/EmailScheduleFilterCard"; import { getEmailSchedules } from "../../../redux/emails/apiService"; +import { CustomBtn } from "../../../shared/Global.styled"; const EmailScheduleForm = ({ handleBack, @@ -276,8 +277,9 @@ const EmailScheduleForm = ({ ]} > - + ); diff --git a/src/modules/Emails/ConfigureEmails/EmailTemplateForm.tsx b/src/modules/Emails/ConfigureEmails/EmailTemplateForm.tsx index 34ee3907..f4b4224b 100644 --- a/src/modules/Emails/ConfigureEmails/EmailTemplateForm.tsx +++ b/src/modules/Emails/ConfigureEmails/EmailTemplateForm.tsx @@ -21,6 +21,7 @@ import EmailTableCard from "../../../components/EmailTableCard"; import ReactQuill from "react-quill"; import React from "react"; import { getEmailTemplates } from "../../../redux/emails/apiService"; +import { CustomBtn } from "../../../shared/Global.styled"; const { Option } = Select; @@ -651,11 +652,9 @@ const EmailTemplateForm = ({ Skip - + {currentFormIndex !== null && ( )} - + + Save + ); }; diff --git a/src/modules/Emails/EmailSchedules/EmailScheduleEditForm.tsx b/src/modules/Emails/EmailSchedules/EmailScheduleEditForm.tsx index f1459d21..6c1614cf 100644 --- a/src/modules/Emails/EmailSchedules/EmailScheduleEditForm.tsx +++ b/src/modules/Emails/EmailSchedules/EmailScheduleEditForm.tsx @@ -2,11 +2,11 @@ import { useState, useEffect } from "react"; import { Form, Input, - Button, Select, message, DatePicker, TimePicker, + Button, } from "antd"; import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; import { updateEmailSchedule } from "../../../redux/emails/emailsActions"; @@ -16,6 +16,7 @@ import FullScreenLoader from "../../../components/Loaders/FullScreenLoader"; import EmailScheduleFilter from "../../../components/EmailScheduleFilter"; import EmailScheduleFilterCard from "../../../components/EmailScheduleFilterCard"; import dayjs from "dayjs"; +import { CustomBtn } from "../../../shared/Global.styled"; const { Option } = Select; const { RangePicker } = DatePicker; @@ -99,6 +100,7 @@ const EmailScheduleEditForm = ({ message.success("Email schedule updated successfully"); fetchEmailSchedules(); } catch (error) { + console.log("Error updating email schedule:", error); message.error("Failed to update email schedule"); } setLoading(false); @@ -113,6 +115,10 @@ const EmailScheduleEditForm = ({ }; if (initialValues.filter_list) { setTableList(initialValues.filter_list); + console.log( + "Filter list in EmailScheduleEditForm:", + initialValues.filter_list + ); } setEmailConfigUID(initialValues.email_config_uid); form.setFieldsValue({ ...formValues }); @@ -168,8 +174,9 @@ const EmailScheduleEditForm = ({ tooltip="Time the email will be sent, actual email delivery time will be after 10 minutes or more since the email is queued for delivery after surveycto data refreshes." > Add Filters for Schedule @@ -189,20 +200,20 @@ const EmailScheduleEditForm = ({ configUID={initialValues.email_config_uid} tableList={tableList} setTableList={(value: any) => { - setTableList(value); + setTableList([value]); }} editingIndex={editingIndex} setEditingIndex={setEditingIndex} />
- + Save +

- {record?.config_name} - - - - - - - handleDeleteConfig(record?.email_config_uid)} - okText="Yes" - cancelText="No" - > +

+ {record?.config_name} + + - - - + + + + + handleDeleteConfig(record?.email_config_uid) + } + okText="Yes" + cancelText="No" + > + + + + +

), @@ -205,7 +204,9 @@ function EmailSchedules({ data, fetchEmailSchedules, sctoForms }: any) { >

Schedule Name : {email_schedule_name}

-

Time : {dayjs(`1970-01-01T${time}`).format("HH:mm")}

+

+ Time : {dayjs(`1970-01-01T${time}`).format("hh:mm A")} +

setIsDeliveryReportModalVisible(-1)} style={{ fontFamily: "Lato", - overflowY: "scroll", - maxHeight: "500px", + ...(deliveryReportData?.length > 10 && { + overflowY: "scroll", + maxHeight: "500px", + }), }} width={"80%"} height={"80%"} @@ -260,6 +263,7 @@ function EmailSchedules({ data, fetchEmailSchedules, sctoForms }: any) { ) : (

@@ -381,13 +385,19 @@ function EmailSchedules({ data, fetchEmailSchedules, sctoForms }: any) { setPaginationPageSize(size), - }} + pagination={ + data.length > 10 + ? { + position: ["topRight"], + pageSize: paginationPageSize, + pageSizeOptions: [10, 25, 50, 100], + showSizeChanger: true, + showQuickJumper: true, + onShowSizeChange: (_, size) => setPaginationPageSize(size), + } + : false + } + bordered={true} /> ) : (

- - + + Save +
{insertTableModelOpen && ( diff --git a/src/modules/Emails/EmailTemplates/EmailTemplates.tsx b/src/modules/Emails/EmailTemplates/EmailTemplates.tsx index 7a7e3f2e..d6651ca2 100644 --- a/src/modules/Emails/EmailTemplates/EmailTemplates.tsx +++ b/src/modules/Emails/EmailTemplates/EmailTemplates.tsx @@ -154,14 +154,20 @@ const EmailTemplates = ({ setPaginationPageSize(size), - }} + pagination={ + templatesData.length > 10 + ? { + position: ["topRight"], + pageSize: paginationPageSize, + pageSizeOptions: [10, 25, 50, 100], + showSizeChanger: true, + showQuickJumper: true, + onShowSizeChange: (_, size) => setPaginationPageSize(size), + } + : false + } rowKey={(record: any) => record.config_type} + bordered={true} /> ) : (
- - Emails -
- -
-
- {isLoading || loading ? ( ) : ( - - - - - {tabId === "manual" ? ( - - ) : tabId === "templates" ? ( - - ) : ( - - )} - - - + <> + + Emails +
+ + {tabId === "manual" + ? "Create Manual Email Trigger" + : tabId === "templates" + ? "Create Email Template" + : "Configure Emails"} + +
+
+ + + + + + + + {tabId === "manual" ? ( + + ) : tabId === "templates" ? ( + + ) : ( + + )} + + + + )} - + + Save + ); diff --git a/src/modules/Emails/ManualTriggers/ManualTriggers.tsx b/src/modules/Emails/ManualTriggers/ManualTriggers.tsx index 673577ef..f5424384 100644 --- a/src/modules/Emails/ManualTriggers/ManualTriggers.tsx +++ b/src/modules/Emails/ManualTriggers/ManualTriggers.tsx @@ -1,7 +1,11 @@ import { useState } from "react"; import { Button, Drawer, Popconfirm, Tooltip, message, Tag } from "antd"; import { Modal } from "antd"; -import { EditOutlined, DeleteOutlined } from "@ant-design/icons"; +import { + EditOutlined, + DeleteOutlined, + ReconciliationOutlined, +} from "@ant-design/icons"; import NotebooksImg from "../../../assets/notebooks.svg"; import { ManualTriggersTable } from "./ManualTriggers.styled"; import ManualEmailTriggerForm from "./ManualTriggerForm"; @@ -44,6 +48,15 @@ function ManualTriggers({ }; return new Date(date).toLocaleDateString("en-US", options); }; + + const format12HourTime = (time: string) => { + const [hours, minutes] = time.split(":"); + const hour = parseInt(hours, 10); + const period = hour >= 12 ? "PM" : "AM"; + const adjustedHour = hour % 12 || 12; + return `${adjustedHour}:${minutes} ${period}`; + }; + const handleDeleteTrigger = async (trigger: any) => { try { const result = await dispatch( @@ -96,21 +109,32 @@ function ManualTriggers({ sorter: (a: any, b: any) => a.config_name.localeCompare(b.config_name), }, { - title: "Trigger Time(UTC)", + title: "Trigger Date", dataIndex: "date", - render: (text: any, record: any) => ( - - {formatDate(record.date)} {record.time} - - ), + render: (text: any, record: any) => { + const options: Intl.DateTimeFormatOptions = { + year: "numeric", + month: "2-digit", + day: "2-digit", + }; + const date = new Date(record.date); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); + const day = String(date.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; + }, sorter: (a: any, b: any) => { const dateA = a.date ? new Date(a.date).getTime() : 0; const dateB = b.date ? new Date(b.date).getTime() : 0; - if (dateA === dateB) { - return a.time.localeCompare(b.time); - } else return dateA - dateB; + return dateA - dateB; }, }, + { + title: "Trigger Time", + dataIndex: "time", + render: (text: any) => format12HourTime(text), + sorter: (a: any, b: any) => a.time.localeCompare(b.time), + }, { title: "Status", dataIndex: "status", @@ -125,62 +149,64 @@ function ManualTriggers({ }, sorter: (a: any, b: any) => a.status.localeCompare(b.status), }, - { - title: "Recipients", - dataIndex: "recipients", - render: (text: any, record: any) => ( - <> - - - {deliveryReportData && deliveryReportData.length > 0 ? ( - - ) : ( -

- No Emails sent yet, delivery reports will be visible after - trigger time. -

- )} -
- - ), - }, + { title: "Action", key: "action", render: (text: any, record: any) => (
+ <> + + 10 && { + overflowY: "scroll", + maxHeight: "500px", + }), + }} + width={"80%"} + height={"80%"} + open={showModal === record.manual_email_trigger_uid} + onOk={handleOk} + onCancel={handleClose} + key={record.manual_email_trigger_uid} + > + {deliveryReportData && deliveryReportData.length > 0 ? ( + + ) : ( +

+ No Emails sent yet, delivery reports will be visible after + trigger time. +

+ )} +
+ -
@@ -203,14 +231,20 @@ function ManualTriggers({ setPaginationPageSize(size), - }} + pagination={ + data.length > 10 + ? { + position: ["topRight"], + pageSize: paginationPageSize, + pageSizeOptions: [10, 25, 50, 100], + showSizeChanger: true, + showQuickJumper: true, + onShowSizeChange: (_, size) => setPaginationPageSize(size), + } + : false + } rowKey={(record: any) => record.manual_email_trigger_uid} + bordered={true} /> ) : (
{ - setSearchParams({ collapsed: collapsed + "" }); - }, [collapsed]); - const { survey_uid } = useParams<{ survey_uid?: string }>() ?? { survey_uid: "", }; @@ -34,56 +26,62 @@ function SideMenu() { const items: MenuProps["items"] = [ { - icon: , label: ( - + + + Email Schedules - + ), key: "emailSchedules", }, { - icon: , label: ( - + + + Manual Triggers - + ), key: "manualTriggers", }, { - icon: , label: ( - + + + Email Templates - + ), key: "emailTemplates", }, ]; - const [currentKey, setCurrentKey] = useState("emailSchedules"); + + const [current, setCurrent] = useState("emailSchedules"); const onClick: MenuProps["onClick"] = (e) => { - setCurrentKey(e.key); + setCurrent(e.key); }; - const getCurrentKey = () => { + const getPossibleKey = () => { const path = location.pathname; if (path.includes("/manual")) return "manualTriggers"; if (path.includes("/schedules")) return "emailSchedules"; @@ -93,38 +91,19 @@ function SideMenu() { }; useEffect(() => { - const key = getCurrentKey(); - setCurrentKey(key); + const key: string = getPossibleKey(); + setCurrent(key); }, []); - const toggle = () => { - setCollapsed(!collapsed); - }; - return ( - setCollapsed(value)} - theme="light" - trigger={null} - > -
-
+ - + ); } diff --git a/src/redux/emails/apiService.ts b/src/redux/emails/apiService.ts index 53b07b17..fd4e915b 100644 --- a/src/redux/emails/apiService.ts +++ b/src/redux/emails/apiService.ts @@ -610,6 +610,31 @@ export const getEmailDeliveryReportSchedule = async ( } }; +export const getEmailDeliveryReportEnumerator = async ( + email_config_uid: string, + email_delivery_report_uid: string +) => { + try { + await getCSRFToken(); + const csrfToken = await getCookie("CSRF-TOKEN"); + + const response = await axios.get( + `${API_BASE_URL}/emails/enumerator_report?email_config_uid=${email_config_uid}&email_delivery_report_uid=${email_delivery_report_uid}`, + { + headers: { + "X-CSRF-Token": csrfToken, + "Content-Type": "application/json", + }, + withCredentials: true, + } + ); + + return response; + } catch (error) { + return error; + } +}; + export const getEmailDeliveryReportTrigger = async ( email_config_uid: string, manual_email_trigger_uid: string @@ -656,5 +681,6 @@ export const api = { getEmailTemplates, getEmailGsheetHeaders, getEmailDeliveryReportSchedule, + getEmailDeliveryReportEnumerator, getEmailDeliveryReportTrigger, };