From aca28484062729fed67ada3e700922bb9a49cd49 Mon Sep 17 00:00:00 2001 From: prafull-opensignlabs Date: Tue, 21 May 2024 20:26:46 +0530 Subject: [PATCH] add Ui for bulk send, changes in reportjson --- apps/OpenSign/src/components/BulkSendUi.js | 215 ++++++++++++++++++ .../shared/fields/SuggestionInput.js | 92 ++++++++ apps/OpenSign/src/constant/Utils.js | 21 ++ apps/OpenSign/src/json/ReportJson.js | 8 + .../src/primitives/GetReportDisplay.js | 46 ++++ apps/OpenSignServer/cloud/main.js | 31 ++- .../cloud/parsefunction/createBatchDocs.js | 151 ++++++++++++ .../cloud/parsefunction/getReport.js | 2 +- .../cloud/parsefunction/reportsJson.js | 1 + 9 files changed, 555 insertions(+), 12 deletions(-) create mode 100644 apps/OpenSign/src/components/BulkSendUi.js create mode 100644 apps/OpenSign/src/components/shared/fields/SuggestionInput.js create mode 100644 apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js diff --git a/apps/OpenSign/src/components/BulkSendUi.js b/apps/OpenSign/src/components/BulkSendUi.js new file mode 100644 index 000000000..039f43c39 --- /dev/null +++ b/apps/OpenSign/src/components/BulkSendUi.js @@ -0,0 +1,215 @@ +import React, { useState, useEffect, useRef } from "react"; +import axios from "axios"; +import SuggestionInput from "./shared/fields/SuggestionInput"; + +const BulkSendUi = (props) => { + const [forms, setForms] = useState([]); + const [formId, setFormId] = useState(2); + const formRef = useRef(null); + const [scrollOnNextUpdate, setScrollOnNextUpdate] = useState(false); + const [isSubmit, setIsSubmit] = useState(false); + useEffect(() => { + if (scrollOnNextUpdate && formRef.current) { + formRef.current.scrollIntoView({ + behavior: "smooth", + block: "end", + inline: "nearest" + }); + setScrollOnNextUpdate(false); + } + }, [forms, scrollOnNextUpdate]); + + useEffect(() => { + (() => { + if (props?.Placeholders?.length > 0) { + let users = []; + props?.Placeholders?.forEach((element) => { + if (!element.signerObjId) { + users = [ + ...users, + { + fieldId: element.Id, + email: "", + label: element.Role, + signer: {} + } + ]; + } + }); + setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]); + } + })(); + // eslint-disable-next-line + }, []); + const handleInputChange = (index, signer, fieldIndex) => { + console.log("index", index); + console.log("signer", signer); + console.log("fieldIndex", fieldIndex); + + const newForms = [...forms]; + newForms[index].fields[fieldIndex].email = signer?.Email + ? signer?.Email + : signer || ""; + newForms[index].fields[fieldIndex].signer = signer?.objectId ? signer : ""; + console.log("newForms[index] ", newForms[index]); + setForms(newForms); + }; + + const handleAddForm = (e) => { + e.preventDefault(); + if (props?.Placeholders.length > 0) { + let newForm = []; + props?.Placeholders?.forEach((element) => { + if (!element.signerObjId) { + newForm = [ + ...newForm, + { + fieldId: element.Id, + email: "", + label: element.Role, + signer: {} + } + ]; + } + }); + setForms([...forms, { Id: formId, fields: newForm }]); + } + setFormId(formId + 1); + setScrollOnNextUpdate(true); + }; + + const handleRemoveForm = (index) => { + const updatedForms = forms.filter((_, i) => i !== index); + setForms(updatedForms); + }; + const handleSubmit = async (e) => { + e.preventDefault(); + e.stopPropagation(); + setIsSubmit(true); + + // Create a copy of Placeholders array from props.item + let Placeholders = [...props.item.Placeholders]; + // Initialize an empty array to store updated documents + let Documents = []; + + // Loop through each form + forms.forEach((form) => { + // Map through the copied Placeholders array to update email values + const updatedPlaceholders = Placeholders.map((placeholder) => { + // Find the field in the current form that matches the placeholder Id + const field = form.fields.find( + (element) => parseInt(element.fieldId) === placeholder.Id + ); + // If a matching field is found, update the email value in the placeholder + const signer = field?.signer?.objectId ? field.signer : {}; + console.log("signer ", signer); + if (field) { + return { + ...placeholder, + email: field.email, + signerObjId: field?.signer?.objectId || "", + signerPtr: signer + }; + } + // If no matching field is found, keep the placeholder as is + return placeholder; + }); + + // Push a new document object with updated Placeholders into the Documents array + Documents.push({ ...props.item, Placeholders: updatedPlaceholders }); + }); + console.log("Documents ", Documents); + // await batchQuery(Documents); + }; + + const batchQuery = async (Documents) => { + const serverUrl = localStorage.getItem("baseUrl"); + const functionsUrl = `${serverUrl}functions/batchdocuments`; + const headers = { + "Content-Type": "application/json", + "X-Parse-Application-Id": localStorage.getItem("parseAppId"), + sessionToken: localStorage.getItem("accesstoken") + }; + const params = { + Documents: JSON.stringify(Documents) + }; + try { + const res = await axios.post(functionsUrl, params, { headers: headers }); + // console.log("res ", res); + if (res.data && res.data.result) { + props.handleClose("success", Documents?.length); + } + } catch (err) { + console.log("Err ", err); + props.handleClose("error", 0); + } finally { + setIsSubmit(false); + } + }; + + return ( + <> + {isSubmit && ( +
+
+
+ )} +
+
+ {forms?.map((form, index) => ( +
+ {form?.fields?.map((field, fieldIndex) => ( +
+ + + handleInputChange(index, signer, fieldIndex) + } + /> +
+ ))} + {index > 0 && ( + + )} +
+
+ ))} +
+
+ + +
+
+ + ); +}; + +export default BulkSendUi; diff --git a/apps/OpenSign/src/components/shared/fields/SuggestionInput.js b/apps/OpenSign/src/components/shared/fields/SuggestionInput.js new file mode 100644 index 000000000..37f2c0b12 --- /dev/null +++ b/apps/OpenSign/src/components/shared/fields/SuggestionInput.js @@ -0,0 +1,92 @@ +import React, { useState, useEffect, useRef } from "react"; +import { findContact } from "../../../constant/Utils"; +const SuggestionInput = (props) => { + const [inputValue, setInputValue] = useState(props?.value || ""); + const [suggestions, setSuggestions] = useState([]); + const [showSuggestions, setShowSuggestions] = useState(false); + const ref = useRef(null); + + useEffect(() => { + document.addEventListener("mousedown", Clickout); + return () => { + document.removeEventListener("mousedown", Clickout); + }; + }, []); + + const Clickout = (event) => { + if (ref.current && !ref.current.contains(event.target)) { + setShowSuggestions(false); + } + }; + // create debounce to avoid unnecessay api calls + useEffect(() => { + let timer; + if (inputValue) { + if (timer) clearTimeout(timer); + timer = setTimeout(() => { + (async () => { + const res = await findContact(inputValue); + if (res?.length > 0) { + setSuggestions(res); + setShowSuggestions(true); + } else { + setSuggestions(res); + setShowSuggestions(false); + } + })(); + }, 1000); + } + return () => clearTimeout(timer); + }, [inputValue]); + + const handleInputChange = async (e) => { + const value = e.target.value; + setInputValue(value); + if (props.onChange) { + props.onChange(value); + } + + if (value.trim() === "") { + setSuggestions([]); + setShowSuggestions(false); + return; + } + }; + const handleSuggestionClick = (suggestion) => { + setInputValue(suggestion.Email); + setSuggestions([]); + setShowSuggestions(false); + if (props.onChange) { + props.onChange(suggestion); + } + }; + return ( +
+ + {showSuggestions && ( + + )} +
+ ); +}; +export default SuggestionInput; diff --git a/apps/OpenSign/src/constant/Utils.js b/apps/OpenSign/src/constant/Utils.js index 93cefe04c..47833399c 100644 --- a/apps/OpenSign/src/constant/Utils.js +++ b/apps/OpenSign/src/constant/Utils.js @@ -2030,3 +2030,24 @@ export const handleSendOTP = async (email) => { alert(error.message); } }; + +export async function findContact(value) { + try { + const currentUser = Parse.User.current(); + const contactbook = new Parse.Query("contracts_Contactbook"); + contactbook.equalTo( + "CreatedBy", + Parse.User.createWithoutData(currentUser.id) + ); + contactbook.notEqualTo("IsDeleted", true); + contactbook.matches("Email", new RegExp(value, "i")); + + const contactRes = await contactbook.find(); + if (contactRes) { + const res = JSON.parse(JSON.stringify(contactRes)); + return res; + } + } catch (error) { + console.error("Error fetching suggestions:", error); + } +} diff --git a/apps/OpenSign/src/json/ReportJson.js b/apps/OpenSign/src/json/ReportJson.js index 8bc528add..f16efca1c 100644 --- a/apps/OpenSign/src/json/ReportJson.js +++ b/apps/OpenSign/src/json/ReportJson.js @@ -360,6 +360,14 @@ export default function reportJson(id) { redirectUrl: "template", action: "redirect" }, + { + btnId: "1631", + btnLabel: "Quick send", + hoverLabel: "Quick send", + btnIcon: "fa-solid fa-envelope", + redirectUrl: "", + action: "bulksend" + }, { btnId: "1834", btnLabel: "Delete", diff --git a/apps/OpenSign/src/primitives/GetReportDisplay.js b/apps/OpenSign/src/primitives/GetReportDisplay.js index 1e82297e5..21b8baf9f 100644 --- a/apps/OpenSign/src/primitives/GetReportDisplay.js +++ b/apps/OpenSign/src/primitives/GetReportDisplay.js @@ -18,6 +18,7 @@ import EditorToolbar, { } from "../components/pdf/EditorToolbar"; import ReactQuill from "react-quill"; import "react-quill/dist/quill.snow.css"; +import BulkSendUi from "../components/BulkSendUi"; const ReportTable = (props) => { const navigate = useNavigate(); @@ -39,6 +40,11 @@ const ReportTable = (props) => { const [mail, setMail] = useState({ subject: "", body: "" }); const [userDetails, setUserDetails] = useState({}); const [isNextStep, setIsNextStep] = useState({}); + const [isBulkSend, setIsBulkSend] = useState({}); + const [templateDeatils, setTemplateDetails] = useState({}); + const [placeholders, setPlaceholders] = useState([]); + const [isErr, setIsErr] = useState(false); + const startIndex = (currentPage - 1) * props.docPerPage; const { isMoreDocs, setIsNextRecord } = props; // For loop is used to calculate page numbers visible below table @@ -211,6 +217,7 @@ const ReportTable = (props) => { }; const handleActionBtn = (act, item) => { + console.log("item", item); if (act.action === "redirect") { handleURL(item, act); } else if (act.action === "delete") { @@ -223,6 +230,8 @@ const ReportTable = (props) => { setIsOption({ [item.objectId]: !isOption[item.objectId] }); } else if (act.action === "resend") { setIsResendMail({ [item.objectId]: true }); + } else if (act.action === "bulksend") { + handleBulkSend(item); } }; // Get current list @@ -605,6 +614,30 @@ const ReportTable = (props) => { ); }; + const handleQuickSendClose = (status, count) => { + setIsBulkSend({}); + setIsAlert(true); + if (status === "success") { + if (count > 1) { + setAlertMsg(count + " Documents sent successfully!"); + } else { + setAlertMsg(count + " Document sent successfully!"); + } + } else { + setIsAlert(true); + setIsErr(true); + } + }; + + const handleBulkSend = (template) => { + if (template?.Placeholders?.length > 0) { + setPlaceholders(template?.Placeholders); + setTemplateDetails(template); + setIsBulkSend({ [template.objectId]: true }); + } else { + setIsDocErr(true); + } + }; return (
{Object.keys(actLoader)?.length > 0 && ( @@ -845,6 +878,19 @@ const ReportTable = (props) => {
)} + {isBulkSend[`${item.objectId}`] && ( + setIsBulkSend({})} + > + + + )} {isShare[item.objectId] && (

Digital Signature Request

" + + document.ExtUserPtr.Name + + ' has requested you to review and sign ' + + document.Name + + ".

Sender " + + sender + + "
Organization " + + orgName + + "
Expires on " + + localExpireDate + + "

This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " + + sender + + ' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ here.

', + }; + const sendMail = await axios.post(url, params, { headers: headers }); + // if (sendMail.data.result.status === 'success') { + // console.log('batch login mail sent'); + // } + } catch (error) { + console.log('error', error); + } + } +} +export default async function createBatchDocs(request) { + const strDocuments = request.params.Documents; + const sessionToken = request.headers['sessiontoken']; + const Documents = JSON.parse(strDocuments); + + // console.log('Documents ', Documents); + const parseConfig = { + baseURL: serverUrl, //localStorage.getItem('baseUrl'), + headers: { + 'X-Parse-Application-Id': appId, + 'X-Parse-Session-Token': sessionToken, + 'Content-Type': 'application/json', + }, + }; + try { + const requests = Documents.map(x => ({ + method: 'POST', + path: '/app/classes/contracts_Document', + body: { + Name: x.Name, + URL: x.URL, + Note: x.Note, + Description: x.Description, + CreatedBy: x.CreatedBy, + ExtUserPtr: { + __type: 'Pointer', + className: x.ExtUserPtr.className, + objectId: x.ExtUserPtr.objectId, + }, + Placeholders: x.Placeholders.map(y => + y?.signerPtr?.objectId + ? { + ...y, + signerPtr: { + __type: 'Pointer', + className: y.signerPtr.className, + objectId: y.signerPtr.objectId, + }, + signerObjId: y.signerObjId, + } + : { ...y, signerPtr: {}, signerObjId: '' } + ), + SignedUrl: x.SignedUrl, + Signers: x.Signers.map(y => ({ + __type: 'Pointer', + className: y.className, + objectId: y.objectId, + })), + }, + })); + // console.log('requests ', requests); + + const response = await axios.post('batch', { requests: requests }, parseConfig); + // // Handle the batch query response + // console.log('Batch query response:', response.data) + if (response.data && response.data.length > 0) { + const updateDocuments = Documents.map((x, i) => ({ + ...x, + objectId: response.data[i]?.success?.objectId, + createdAt: response.data[i]?.success.createdAt, + })); + for (let i = 0; i < updateDocuments.length; i++) { + // console.log('updateDocuments ', updateDocuments); + sendMail(updateDocuments[i], sessionToken); + } + return 'success'; + } + + // Handle individual responses within response.data.results + } catch (error) { + console.error('Error performing batch query:', error); + } +} diff --git a/apps/OpenSignServer/cloud/parsefunction/getReport.js b/apps/OpenSignServer/cloud/parsefunction/getReport.js index 8749c2b28..cd101f7c2 100644 --- a/apps/OpenSignServer/cloud/parsefunction/getReport.js +++ b/apps/OpenSignServer/cloud/parsefunction/getReport.js @@ -30,7 +30,7 @@ export default async function getReport(request) { 'X-Parse-Application-Id': appId, 'X-Parse-Master-Key': process.env.MASTER_KEY, }; - const url = `${serverUrl}/classes/${clsName}?where=${strParams}&keys=${strKeys}&order=${orderBy}&skip=${skip}&limit=${limit}&include=AuditTrail.UserPtr`; + const url = `${serverUrl}/classes/${clsName}?where=${strParams}&keys=${strKeys}&order=${orderBy}&skip=${skip}&limit=${limit}&include=AuditTrail.UserPtr,Placeholders.signerPtr`; const res = await axios.get(url, { headers: headers }); if (res.data && res.data.results) { return res.data.results; diff --git a/apps/OpenSignServer/cloud/parsefunction/reportsJson.js b/apps/OpenSignServer/cloud/parsefunction/reportsJson.js index 98d2c06b5..e541b5439 100644 --- a/apps/OpenSignServer/cloud/parsefunction/reportsJson.js +++ b/apps/OpenSignServer/cloud/parsefunction/reportsJson.js @@ -323,6 +323,7 @@ export default function reportJson(id, userId) { 'Signers.Name', 'Signers.Email', 'Signers.Phone', + 'Placeholders', ], }; default: