diff --git a/apps/OpenSign/public/locales/de/translation.json b/apps/OpenSign/public/locales/de/translation.json
index 32b2b682b..f3c95c4c8 100644
--- a/apps/OpenSign/public/locales/de/translation.json
+++ b/apps/OpenSign/public/locales/de/translation.json
@@ -361,6 +361,7 @@
"date": "Datum",
"text": "Text",
"text input": "Texteingabe",
+ "cells": "Zellen",
"checkbox": "Checkbox",
"dropdown": "Dropdown",
"radio button": "Radiobutton",
@@ -415,10 +416,12 @@
"options": "Optionen",
"minimun-check": "Minimale Anzahl",
"maximum-check": "Maximale Anzahl",
+ "cell-count": "Zellzahl",
"default-value": "Standardwert",
"select": "Auswählen",
- "read-only": "Nur lesen",
+ "read-only": "Ist schreibgeschützt",
"hide-labels": "Labels ausblenden",
+ "layout": "Layout",
"checkbox": "Checkbox",
"alert": "Warnung",
"zoom-in": "Vergrößern",
@@ -1025,5 +1028,6 @@
"not-found-in-signature": "Nicht in Signatur gefunden",
"readonly-textinput-error": "Schreibgeschütztes Text-Widget muss einen Standardwert haben oder optional sein.",
"readonly-dropdown-error": "Schreibgeschütztes Dropdown-Widget muss einen Standardwert haben oder optional sein.",
- "readonly-radiobtn-error": "Schreibgeschütztes Optionsfeld-Widget muss einen Standardwert haben oder optional sein."
+ "readonly-radiobtn-error": "Schreibgeschütztes Optionsfeld-Widget muss einen Standardwert haben oder optional sein.",
+ "choose-one":"Wählen Sie eine aus"
}
diff --git a/apps/OpenSign/public/locales/en/translation.json b/apps/OpenSign/public/locales/en/translation.json
index 39d377405..da3832002 100644
--- a/apps/OpenSign/public/locales/en/translation.json
+++ b/apps/OpenSign/public/locales/en/translation.json
@@ -361,6 +361,7 @@
"date": "date",
"text": "text",
"text input": "text input",
+ "cells": "cells",
"checkbox": "checkbox",
"dropdown": "dropdown",
"radio button": "radio button",
@@ -415,10 +416,12 @@
"options": "Options",
"minimun-check": "Minimun check",
"maximum-check": "Maximum check",
+ "cell-count": "Cell count",
"default-value": "Default value",
"select": "Select",
- "read-only": "Is read only",
+ "read-only": "read only",
"hide-labels": "Hide labels",
+ "layout": "Layout",
"checkbox": "Checkbox",
"alert": "Alert",
"zoom-in": "Zoom in",
@@ -1025,5 +1028,6 @@
"not-found-in-signature": "Not found in signature",
"readonly-textinput-error": "Read-only text widget must have a default value or you can make it optional.",
"readonly-dropdown-error": "Read-only dropdown widget must have a default value or you can make it optional.",
- "readonly-radiobtn-error": "Read-only radio button widget must have a default value or you can make it optional."
+ "readonly-radiobtn-error": "Read-only radio button widget must have a default value or you can make it optional.",
+ "choose-one":"Choose One"
}
\ No newline at end of file
diff --git a/apps/OpenSign/public/locales/es/translation.json b/apps/OpenSign/public/locales/es/translation.json
index 551e3104d..aea3b6f8c 100644
--- a/apps/OpenSign/public/locales/es/translation.json
+++ b/apps/OpenSign/public/locales/es/translation.json
@@ -362,6 +362,7 @@
"date": "fecha",
"text": "texto",
"text input": "entrada de texto",
+ "cells": "células",
"checkbox": "casilla",
"dropdown": "desplegable",
"radio button": "botón de radio",
@@ -416,10 +417,12 @@
"options": "Opciones",
"minimun-check": "Chequeo mínimo",
"maximum-check": "Chequeo máximo",
+ "cell-count": "recuento de células",
"default-value": "Valor por defecto",
"select": "Seleccionar",
"read-only": "Es de solo lectura",
"hide-labels": "Esconder etiquetas",
+ "layout": "Diseño",
"checkbox": "Casilla",
"alert": "Alerta",
"zoom-in": "Acercar",
@@ -1025,5 +1028,6 @@
"not-found-in-signature": "No encontrado en la firma",
"readonly-textinput-error": "El widget de texto de solo lectura debe tener un valor predeterminado o puede hacerse opcional.",
"readonly-dropdown-error": "El widget desplegable de solo lectura debe tener un valor predeterminado o puede hacerse opcional.",
- "readonly-radiobtn-error": "El widget de botón de opción de solo lectura debe tener un valor predeterminado o puede hacerse opcional."
+ "readonly-radiobtn-error": "El widget de botón de opción de solo lectura debe tener un valor predeterminado o puede hacerse opcional.",
+ "choose-one":"Elige uno"
}
diff --git a/apps/OpenSign/public/locales/fr/translation.json b/apps/OpenSign/public/locales/fr/translation.json
index b2d558179..ef15a3cde 100644
--- a/apps/OpenSign/public/locales/fr/translation.json
+++ b/apps/OpenSign/public/locales/fr/translation.json
@@ -361,6 +361,7 @@
"date": "date",
"text": "texte",
"text input": "saisie de texte",
+ "cells": "cellules",
"checkbox": "case à cocher",
"dropdown": "dérouler",
"radio button": "bouton radio",
@@ -415,10 +416,12 @@
"options": "Possibilités",
"minimun-check": "Vérification minimale",
"maximum-check": "Contrôle maximum",
+ "cell-count": "numération cellulaire",
"default-value": "Valeur par défaut",
"select": "Sélectionner",
"read-only": "Est en lecture seule",
"hide-labels": "Masquer les étiquettes",
+ "layout": "Disposition",
"checkbox": "Case à cocher",
"alert": "Alerte",
"zoom-in": "Agrandir",
@@ -1025,5 +1028,6 @@
"not-found-in-signature": "Introuvable dans la signature",
"readonly-textinput-error": "Le widget de texte en lecture seule doit avoir une valeur par défaut ou être rendu optionnel.",
"readonly-dropdown-error": "Le widget déroulant en lecture seule doit avoir une valeur par défaut ou être rendu optionnel.",
- "readonly-radiobtn-error": "Le widget bouton radio en lecture seule doit avoir une valeur par défaut ou être rendu optionnel."
+ "readonly-radiobtn-error": "Le widget bouton radio en lecture seule doit avoir une valeur par défaut ou être rendu optionnel.",
+ "choose-one":"Choisissez-en un"
}
diff --git a/apps/OpenSign/public/locales/hi/translation.json b/apps/OpenSign/public/locales/hi/translation.json
index aefb718d3..5415755a5 100644
--- a/apps/OpenSign/public/locales/hi/translation.json
+++ b/apps/OpenSign/public/locales/hi/translation.json
@@ -361,6 +361,7 @@
"date": "दिनांक",
"text": "पाठ",
"text input": "पाठ इनपुट",
+ "cells": "सेल्स",
"checkbox": "चेकबॉक्स",
"dropdown": "ड्रॉपडाउन",
"radio button": "रेडियो बटन",
@@ -415,10 +416,12 @@
"options": "विकल्प",
"minimun-check": "न्यूनतम जाँच",
"maximum-check": "अधिकतम जाँच",
+ "cell-count": "सेल्स संख्या",
"default-value": "डिफ़ॉल्ट मान",
"select": "चुनें",
- "read-only": "केवल पढ़ने के लिए है",
+ "read-only": "सिर्फ पढ़ने के लिए है",
"hide-labels": "लेबल छिपाएँ",
+ "layout": "लेआउट",
"checkbox": "चेकबॉक्स",
"alert": "चेतावनी",
"zoom-in": "ज़ूम इन करें",
@@ -1025,5 +1028,6 @@
"not-found-in-signature": "हस्ताक्षर में नहीं मिला",
"readonly-textinput-error": "रीड-ओनली टेक्स्ट विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।",
"readonly-dropdown-error": "रीड-ओनली ड्रॉपडाउन विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।",
- "readonly-radiobtn-error": "रीड-ओनली रेडियो बटन विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।"
+ "readonly-radiobtn-error": "रीड-ओनली रेडियो बटन विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।",
+ "choose-one":"एक का चयन"
}
diff --git a/apps/OpenSign/public/locales/it/translation.json b/apps/OpenSign/public/locales/it/translation.json
index b164e80fe..5d7713f67 100644
--- a/apps/OpenSign/public/locales/it/translation.json
+++ b/apps/OpenSign/public/locales/it/translation.json
@@ -361,6 +361,7 @@
"date": "data",
"text": "testo",
"text input": "campo di testo",
+ "cells": "cellule",
"checkbox": "casella di controllo",
"dropdown": "menu a tendina",
"radio button": "pulsante di opzione",
@@ -415,10 +416,12 @@
"options": "Opzioni",
"minimun-check": "Controllo minimo",
"maximum-check": "Controllo massimo",
+ "cell-count": "conteggio delle cellule",
"default-value": "Valore predefinita",
"select": "Seleziona",
- "read-only": "È solo lettura",
+ "read-only": "È di sola lettura",
"hide-labels": "Nascondi etichette",
+ "layout": "Layout",
"checkbox": "Casella di controllo",
"alert": "Avviso",
"zoom-in": "Ingrandisci",
@@ -1025,5 +1028,6 @@
"not-found-in-signature": "Non trovato nella firma",
"readonly-textinput-error": "Il widget di testo di sola lettura deve avere un valore predefinito oppure può essere reso opzionale.",
"readonly-dropdown-error": "Il widget a discesa di sola lettura deve avere un valore predefinito oppure può essere reso opzionale.",
- "readonly-radiobtn-error": "Il widget pulsante di opzione di sola lettura deve avere un valore predefinito oppure può essere reso opzionale."
+ "readonly-radiobtn-error": "Il widget pulsante di opzione di sola lettura deve avere un valore predefinito oppure può essere reso opzionale.",
+ "choose-one":"Scegline uno"
}
diff --git a/apps/OpenSign/src/components/pdf/CellsWidget.jsx b/apps/OpenSign/src/components/pdf/CellsWidget.jsx
new file mode 100644
index 000000000..d00b4a393
--- /dev/null
+++ b/apps/OpenSign/src/components/pdf/CellsWidget.jsx
@@ -0,0 +1,123 @@
+import { useState, useEffect, useRef } from "react";
+
+const Cell = ({
+ count,
+ h,
+ value,
+ editable,
+ onChange,
+ onKeyDown,
+ inputRef,
+ index
+}) => (
+
+ onChange && onChange(e, index) : undefined}
+ onKeyDown={editable ? (e) => onKeyDown && onKeyDown(e, index) : undefined}
+ className="w-full text-center uppercase focus:outline-none bg-transparent text-[12px]"
+ style={{ fontFamily: "Arial, sans-serif" }}
+ />
+
+);
+
+export default function CellsWidget({
+ count = 8,
+ height = 40,
+ value = "",
+ editable = false,
+ onChange,
+ onKeyDown,
+ onCellCountChange,
+ inputRefs,
+ resizable = false
+}) {
+ const [cellCount, setCellCount] = useState(count);
+
+ // keep internal state in sync with prop updates
+ useEffect(() => setCellCount(count), [count]);
+
+ const startX = useRef(0);
+ const startCount = useRef(cellCount);
+
+ const capture = (downEv, onMove, onUp = () => {}) => {
+ const id = downEv.pointerId;
+ const move = (ev) => id === ev.pointerId && onMove(ev);
+ const up = (ev) => {
+ if (id !== ev.pointerId) return;
+ onUp(ev);
+ downEv.target.releasePointerCapture(id);
+ window.removeEventListener("pointermove", move);
+ window.removeEventListener("pointerup", up);
+ };
+ window.addEventListener("pointermove", move);
+ window.addEventListener("pointerup", up);
+ downEv.target.setPointerCapture(id);
+ };
+
+ const onTopHandlePointerDown = (ev) => {
+ // Prevent triggering the widget drag logic
+ ev.stopPropagation();
+ ev.preventDefault();
+ startX.current = ev.clientX;
+ startCount.current = cellCount;
+ capture(ev, (moveEv) => {
+ const dx = moveEv.clientX - startX.current;
+ const delta = Math.floor(-dx / 5);
+ let newCount = Math.max(1, startCount.current + delta);
+ if (newCount !== cellCount) {
+ setCellCount(newCount);
+ onCellCountChange?.(newCount);
+ }
+ });
+ };
+
+ const cells = Array.from({ length: cellCount }).map((_, i) => value[i] || "");
+
+ return (
+
+ {resizable && (
+
+ )}
+ {cells.map((val, i) => (
+
(inputRefs.current[i] = el) : undefined}
+ index={i}
+ />
+ ))}
+ |
+ );
+}
diff --git a/apps/OpenSign/src/components/pdf/DropdownWidgetOption.jsx b/apps/OpenSign/src/components/pdf/DropdownWidgetOption.jsx
index 7a7800f30..e6fefc339 100644
--- a/apps/OpenSign/src/components/pdf/DropdownWidgetOption.jsx
+++ b/apps/OpenSign/src/components/pdf/DropdownWidgetOption.jsx
@@ -7,8 +7,8 @@ import { fontColorArr, fontsizeArr } from "../../constant/Utils";
function DropdownWidgetOption(props) {
const { t } = useTranslation();
const [dropdownOptionList, setDropdownOptionList] = useState([
- "option-1",
- "option-2"
+ "Option-1",
+ "Option-2"
]);
const [minCount, setMinCount] = useState(0);
const [maxCount, setMaxCount] = useState(0);
@@ -17,11 +17,13 @@ function DropdownWidgetOption(props) {
const [isHideLabel, setIsHideLabel] = useState(false);
const [status, setStatus] = useState("required");
const [defaultValue, setDefaultValue] = useState("");
- const statusArr = ["required", "optional"];
const [defaultCheckbox, setDefaultCheckbox] = useState([]);
+ const [layout, setLayout] = useState("vertical");
+ const statusArr = ["required", "optional"];
+ const layoutArr = ["vertical", "horizontal"];
const resetState = () => {
- setDropdownOptionList(["option-1", "option-2"]);
+ setDropdownOptionList(["Option-1", "Option-2"]);
setDropdownName(props.currWidgetsDetails?.options?.name || props.type);
setIsReadOnly(false);
setIsHideLabel(false);
@@ -29,6 +31,7 @@ function DropdownWidgetOption(props) {
setMaxCount(0);
setDefaultCheckbox([]);
setDefaultValue("");
+ setLayout("vertical");
};
useEffect(() => {
if (
@@ -48,6 +51,7 @@ function DropdownWidgetOption(props) {
setStatus(props.currWidgetsDetails?.options?.status || "required");
setDefaultValue(props.currWidgetsDetails?.options?.defaultValue || "");
setDefaultCheckbox(props.currWidgetsDetails?.options?.defaultValue || []);
+ setLayout(props.currWidgetsDetails?.options?.layout || "vertical");
} else {
setStatus("required");
resetState();
@@ -107,6 +111,9 @@ function DropdownWidgetOption(props) {
props?.type === "dropdown" || props?.type === radioButtonWidget;
const readOnlyWithoutValue =
isReadOnly && !defaultValue && status !== "optional";
+ const WidgetLayout = ["checkbox", radioButtonWidget].includes(props.type)
+ ? layout
+ : null;
// If it’s a dropdown and it’s read-only without a value (nor marked optional), stop here.
if (isDropdownOrRadio && readOnlyWithoutValue) {
@@ -129,7 +136,8 @@ function DropdownWidgetOption(props) {
null,
status,
defaultData,
- isHideLabel
+ isHideLabel,
+ WidgetLayout
);
resetState();
};
@@ -153,17 +161,18 @@ function DropdownWidgetOption(props) {
}}
>
-
+
{t("name")}
*
e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
- required
value={dropdownName}
onChange={(e) => setDropdownName(e.target.value)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
+ required
/>
@@ -245,27 +254,23 @@ function DropdownWidgetOption(props) {
>
)}
{props.type !== "checkbox" && (
- <>
-
- {statusArr.map((data, ind) => {
- return (
-
-
setStatus(data.toLowerCase())}
- checked={status.toLowerCase() === data.toLowerCase()}
- />
-
{data}
-
- );
- })}
-
- >
+
+ {statusArr.map((data, ind) => (
+
+
setStatus(data.toLowerCase())}
+ checked={status.toLowerCase() === data.toLowerCase()}
+ />
+
{data}
+
+ ))}
+
)}
{t("font-size")} :
@@ -286,8 +291,8 @@ function DropdownWidgetOption(props) {
);
})}
-
-
{t("color")} :
+
+ {t("color")} :
setIsReadOnly(e.target.checked)}
/>
-
+
{t("read-only")}
@@ -344,15 +349,42 @@ function DropdownWidgetOption(props) {
onChange={(e) => setIsHideLabel(e.target.checked)}
/>
-
+
{t("hide-labels")}
)}
)}
+ {["checkbox", radioButtonWidget].includes(props.type) && (
+ <>
+
+ {t("layout")}
+
+
+ {layoutArr.map((data, ind) => (
+
+ setLayout(data.toLowerCase())}
+ />
+
+ {data}
+
+
+ ))}
+
+ >
+ )}
-
{
+ const isSignerList = props.xyPosition.some((d) => d.signerPtr);
+ if (isSignerList) {
+ const signerId = props.data?.Id || props.uniqueId;
+ const filterSignerPos = props.xyPosition.filter((d) => d.Id === signerId);
+ if (filterSignerPos.length > 0) {
+ const getPlaceHolder = filterSignerPos[0].placeHolder;
+ const updatedPlaceHolder = getPlaceHolder.map((ph) => {
+ if (ph.pageNumber !== props.pageNumber) return ph;
+ const newPos = ph.pos.map((p) =>
+ p.key === key
+ ? { ...p, options: { ...p.options, cellCount: newCount } }
+ : p
+ );
+ return { ...ph, pos: newPos };
+ });
+ const newSignerPos = props.xyPosition.map((obj) =>
+ obj.Id === signerId
+ ? { ...obj, placeHolder: updatedPlaceHolder }
+ : obj
+ );
+ props.setXyPosition(newSignerPos);
+ }
+ } else {
+ const updatePos = props.xyPosition[props.index].pos.map((p) =>
+ p.key === key
+ ? { ...p, options: { ...p.options, cellCount: newCount } }
+ : p
+ );
+ const updatePlaceholder = props.xyPosition.map((obj, ind) =>
+ ind === props.index ? { ...obj, pos: updatePos } : obj
+ );
+ props.setXyPosition(updatePlaceholder);
+ }
+ };
const PlaceholderIcon = () => {
// 1- If props.isShowBorder is true, display border's icon for all widgets. OR
// 2- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show border's icon:
@@ -751,16 +792,9 @@ function Placeholder(props) {
id={props.pos.key}
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
key={props.pos.key}
+ cancel=".cell-size-handle"
lockAspectRatio={
!props.isFreeResize &&
- ![
- textWidget,
- "email",
- "name",
- "company",
- "job title",
- textInputWidget
- ].includes(props.pos.type) &&
(props.pos.Width
? props.pos.Width / props.pos.Height
: defaultWidthHeight(props.pos.type).width /
@@ -820,8 +854,10 @@ function Placeholder(props) {
: props.posHeight(props.pos, props.isSignYourself)
}}
minHeight={
- props.pos.type !== "checkbox" &&
- calculateFont(props.pos.options?.fontSize, true)
+ props.pos.type === cellsWidget
+ ? calculateFont(props.pos.options?.fontSize, true)
+ : props.pos.type !== "checkbox" &&
+ calculateFont(props.pos.options?.fontSize, true)
}
maxHeight="auto"
onResizeStart={() => {
@@ -938,6 +974,7 @@ function Placeholder(props) {
handleSaveDate={handleSaveDate}
xPos={props.xPos}
calculateFont={calculateFont}
+ setCellCount={setCellCount}
/>
@@ -945,11 +982,11 @@ function Placeholder(props) {
-
-
{t("format")} :
-
+
+
+ {t("format")} :
{
const selectedIndex = e.target.value;
@@ -973,23 +1010,23 @@ function Placeholder(props) {
{selectDate.format}
-
-
{t("font-size")} :
-
props.setFontSize(parseInt(e.target.value))}
- >
- {fontsizeArr.map((size, ind) => {
- return (
+
+
+
{t("font-size")} :
+
props.setFontSize(parseInt(e.target.value))}
+ >
+ {fontsizeArr.map((size, ind) => (
{size}
- );
- })}
-
-
- {t("color")}:
+ ))}
+
+
+
+ {t("color")} :
props.setFontColor(e.target.value)}
className="ml-[4px] md:ml[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
>
- {fontColorArr.map((color, ind) => {
- return (
-
- {t(`color-type.${color}`)}
-
- );
- })}
+ {fontColorArr.map((color, ind) => (
+
+ {t(`color-type.${color}`)}
+
+ ))}
-
+ {props?.isPlaceholder && (
+
+ setIsDateReadOnly(!isDateReadOnly)}
+ />
+
+ {t("read-only")}
+
+
+ )}
@@ -148,41 +152,42 @@ function PlaceholderType(props) {
);
case "checkbox":
+ const checkBoxLayout = props.pos.options?.layout || "vertical";
+ const isMultipleCheckbox =
+ props.pos.options?.values?.length > 0 ? true : false;
+ const checkBoxWrapperClass = `flex items-start ${
+ checkBoxLayout === "horizontal"
+ ? `flex-row flex-wrap ${isMultipleCheckbox ? "gap-x-2" : ""}`
+ : `flex-col ${isMultipleCheckbox ? "gap-y-[5px]" : ""}`
+ }`; // Using gap-y-1 for consistency, adjust if needed
+
return (
-
- {props.pos.options?.values?.map((data, ind) => {
- return (
-
- 0 ? "mt-[3px]" : "mt-[0px]"
- }`}
- >
-
- {!props.pos.options?.isHideLabel && (
- {data}
- )}
-
-
- );
- })}
+
+ {props.pos.options?.values?.map((data, ind) => (
+
+
+
+ {!props.pos.options?.isHideLabel && (
+ {data}
+ )}
+
+
+ ))}
);
case textInputWidget:
@@ -192,24 +197,15 @@ function PlaceholderType(props) {
placeholder={hint || t("widgets-name.text")}
rows={1}
value={widgetValue}
- className={`${
- props.pos.options?.isReadOnly ||
- props.data?.signerObjId !== props.signerObjId
- ? "select-none"
- : textWidgetCls
- }`}
+ className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
style={{
fontSize: fontSize,
color: fontColor,
- background: props.data?.blockColor,
+ background: isReadOnly ? props.data?.blockColor : "white",
pointerEvents: "none"
}}
readOnly
- disabled={
- props.isNeedSign &&
- (props.pos.options?.isReadOnly ||
- props.data?.signerObjId !== props.signerObjId)
- }
+ disabled={props.isNeedSign && isReadOnly}
cols="50"
/>
) : (
@@ -217,13 +213,31 @@ function PlaceholderType(props) {
{hint || widgetTypeTranslation}
);
+ case cellsWidget: {
+ const count = props.pos.options?.cellCount || 5;
+ const cells = (widgetValue || "").split("");
+ const height = "100%";
+ const handleCellResize = (newCount) => {
+ if (props.setCellCount) props.setCellCount(props.pos.key, newCount);
+ };
+ return (
+
+ );
+ }
case "dropdown":
return (
- {widgetData || hint || widgetTypeTranslation}
+ {widgetData || t("choose-one")}
);
@@ -259,13 +273,15 @@ function PlaceholderType(props) {
placeholder={hint || widgetTypeTranslation}
rows={1}
value={widgetValue}
- className={textWidgetCls}
+ className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
style={{
fontSize: fontSize,
color: fontColor,
+ background: isReadOnly ? props.data?.blockColor : "white",
pointerEvents: "none"
}}
cols="50"
+ disabled={props.isNeedSign && isReadOnly}
/>
) : (
@@ -280,13 +296,15 @@ function PlaceholderType(props) {
placeholder={hint || widgetTypeTranslation}
rows={1}
value={widgetValue}
- className={textWidgetCls}
+ className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
style={{
fontSize: fontSize,
color: fontColor,
+ background: isReadOnly ? props.data?.blockColor : "white",
pointerEvents: "none"
}}
cols="50"
+ disabled={props.isNeedSign && isReadOnly}
/>
) : (
@@ -301,13 +319,15 @@ function PlaceholderType(props) {
placeholder={hint || widgetTypeTranslation}
rows={1}
value={widgetValue}
- className={textWidgetCls}
+ className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
style={{
fontSize: fontSize,
color: fontColor,
+ background: isReadOnly ? props.data?.blockColor : "white",
pointerEvents: "none"
}}
cols="50"
+ disabled={props.isNeedSign && isReadOnly}
/>
) : (
@@ -405,15 +425,15 @@ function PlaceholderType(props) {
placeholder={hint || widgetTypeTranslation}
rows={1}
value={widgetValue}
- className={textWidgetCls}
+ className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
style={{
fontSize: fontSize,
color: fontColor,
- fontFamily: "Arial, sans-serif",
+ background: isReadOnly ? props.data?.blockColor : "white",
pointerEvents: "none"
}}
- disabled
cols="1"
+ disabled={props.isNeedSign && isReadOnly}
/>
) : (
@@ -421,46 +441,39 @@ function PlaceholderType(props) {
);
case radioButtonWidget:
+ const radioLayout = props.pos.options?.layout || "vertical";
+ const isOnlyOneBtn = props.pos.options?.values?.length > 0 ? true : false;
+ const radioWrapperClass = `flex items-start ${
+ radioLayout === "horizontal"
+ ? `flex-row flex-wrap ${isOnlyOneBtn ? "gap-x-2" : ""}`
+ : `flex-col ${isOnlyOneBtn ? "gap-y-[5px]" : ""}`
+ }`; // Using gap-y-1 for consistency, adjust if needed
return (
-
- {props.pos.options?.values.map((data, ind) => {
- return (
-
- 0 ? "5px" : "0px"
- }}
- className="text-xs mb-0 flex items-center gap-1 "
- >
-
- {!props.pos.options?.isHideLabel && (
- {data}
- )}
-
-
- );
- })}
+
+ {props.pos.options?.values.map((data, ind) => (
+
+
+
+ {!props.pos.options?.isHideLabel && (
+ {data}
+ )}
+
+
+ ))}
);
case textWidget:
@@ -474,7 +487,8 @@ function PlaceholderType(props) {
style={{
fontFamily: "Arial, sans-serif",
fontSize: fontSize,
- color: fontColor
+ color: fontColor,
+ background: "white"
}}
cols="50"
/>
diff --git a/apps/OpenSign/src/components/pdf/RenderPdf.jsx b/apps/OpenSign/src/components/pdf/RenderPdf.jsx
index 8d69ab6bf..80f4c8b86 100644
--- a/apps/OpenSign/src/components/pdf/RenderPdf.jsx
+++ b/apps/OpenSign/src/components/pdf/RenderPdf.jsx
@@ -79,7 +79,7 @@ function RenderPdf(props) {
}
};
- //function for render placeholder block over pdf document
+ // function for render placeholder block over pdf document (all signing flow)
const checkSignedSigners = (data) => {
let checkSign = [];
//condition to handle quick send flow and using normal request sign flow
@@ -91,73 +91,71 @@ function RenderPdf(props) {
: [];
return (
checkSign.length === 0 &&
- data?.placeHolder?.map((placeData, key) => {
- return (
-
- {placeData.pageNumber === props.pageNumber &&
- placeData.pos.map((pos, ind) => {
- return (
- pos && (
-
-
-
- )
- );
- })}
-
- );
- })
+ data?.placeHolder?.map((placeData, key) => (
+
+ {placeData.pageNumber === props.pageNumber &&
+ placeData.pos.map(
+ (pos, ind) =>
+ pos && (
+
+
+
+ )
+ )}
+
+ ))
);
};
@@ -172,148 +170,138 @@ function RenderPdf(props) {
}
};
const pdfDataBase64 = `data:application/pdf;base64,${props.pdfBase64Url}`;
- //calculate render height of pdf in mobile view
+ // calculate render height of pdf in mobile view
const handlePageLoadSuccess = (page) => {
- const containerWidth = props.divRef.current.offsetWidth; // Get container width
- const viewport = page.getViewport({ scale: 1 });
- const scale = containerWidth / viewport.width; // Scale to fit container width
- const scaleHeight = viewport.height * scale;
- setScaledHeight(scaleHeight);
+ if (isMobile) {
+ const containerWidth = props.divRef.current.offsetWidth; // Get container width
+ const viewport = page.getViewport({ scale: 1 });
+ const scale = containerWidth / viewport.width; // Scale to fit container width
+ const scaleHeight = viewport.height * scale;
+ setScaledHeight(scaleHeight);
+ }
};
return (
<>
{props.successEmail && (
{t("success-email-alert")}
)}
- {isMobile ? (
-
+
-
- {props.containerWH?.width &&
- props.pdfOriginalWH.length > 0 &&
- (props.pdfRequest || props.isSelfSign
- ? props.signerPos?.map((data, key) => {
- return (
+ {props.pdfLoad !== false &&
+ props.containerWH?.width &&
+ props.pdfOriginalWH.length > 0 && (
+ <>
+ {props.pdfRequest || props.isSelfSign
+ ? // request sign, guest sign,
+ props.signerPos?.map((data, key) => (
{checkSignedSigners(data)}
- );
- })
- : props.placeholder // placeholder mobile
- ? props.signerPos?.map((data, ind) => {
- return (
+ ))
+ : props.placeholder // placeholdersign document, draft document, create template, draft template
+ ? props.signerPos?.map((data, ind) => (
{data?.placeHolder &&
- data?.placeHolder.map((placeData, index) => {
- return (
-
- {placeData.pageNumber === props.pageNumber &&
- placeData.pos.map((pos) => {
- return (
-
-
-
- );
- })}
-
- );
- })}
+ data?.placeHolder.map((placeData, index) => (
+
+ {placeData.pageNumber === props.pageNumber &&
+ placeData.pos.map((pos) => (
+
+
+
+ ))}
+
+ ))}
- );
- })
- : !props.pdfDetails?.[0]?.IsCompleted &&
- props.xyPosition?.map((data, ind) => {
- return (
+ ))
+ : !props.pdfDetails?.[0]?.IsCompleted && // signyourself flow
+ props.xyPosition?.map((data, ind) => (
{data.pageNumber === props.pageNumber &&
- data.pos.map((pos, id) => {
- return (
+ data.pos.map(
+ (pos, id) =>
pos && (
)
- );
- })}
+ )}
- );
- }))}
- {/* Mobile */}
-
{t("failed-to-load-refresh-page")}}
- onLoadError={() => props.setPdfLoad(false)}
- loading={t("loading-doc")}
- onLoadSuccess={props.pageDetails}
- onClick={() =>
- props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
- }
- file={pdfDataBase64}
- >
- {
- console.log("annotation error", error);
- }}
- className="select-none touch-callout-none"
- />
-
-
-
- ) : (
-
-
+ )}
+ {t("failed-to-load-refresh-page")}}
+ onLoadError={(e) => {
+ console.log("PDF load error", e);
+ props.setPdfLoad(false);
}}
- ref={props.drop}
- id="container"
+ loading={t("loading-doc")}
+ onLoadSuccess={(pdf) => {
+ props.setPdfLoad(true);
+ props.pageDetails(pdf);
+ }}
+ onClick={() =>
+ props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
+ }
+ file={pdfDataBase64}
>
- {props.pdfLoad &&
- props.containerWH?.width &&
- props.pdfOriginalWH.length > 0 &&
- (props.pdfRequest || props.isSelfSign //pdf request sign flow
- ? props.signerPos?.map((data, key) => {
- return (
-
- {checkSignedSigners(data)}
-
- );
- })
- : props.placeholder //placeholder and template flow
- ? props.signerPos.map((data, ind) => {
- return (
-
- {data?.placeHolder &&
- data?.placeHolder.map((placeData, index) => {
- return (
-
- {placeData.pageNumber === props.pageNumber &&
- placeData.pos.map((pos) => {
- return (
-
-
-
- );
- })}
-
- );
- })}
-
- );
- })
- : !props.pdfDetails?.[0]?.IsCompleted &&
- props.xyPosition?.map((data, ind) => {
- // signyourself flow
- return (
-
- {data.pageNumber === props.pageNumber &&
- data.pos.map((pos) => {
- return (
-
-
- props.handleStop(
- event,
- dragElement,
- pos.type
- )
- }
- handleSignYourselfImageResize={
- handleSignYourselfImageResize
- }
- index={props.index}
- xyPosition={props.xyPosition}
- setXyPosition={props.setXyPosition}
- isShowBorder={true}
- isSignYourself={true}
- posWidth={posWidth}
- posHeight={posHeight}
- pdfDetails={props.pdfDetails[0]}
- isDragging={props.isDragging}
- setIsCheckbox={props.setIsCheckbox}
- setCurrWidgetsDetails={
- props.setCurrWidgetsDetails
- }
- handleTextSettingModal={
- props.handleTextSettingModal
- }
- scale={props.scale}
- containerWH={props.containerWH}
- pdfOriginalWH={props.pdfOriginalWH}
- pageNumber={props.pageNumber}
- fontSize={props.fontSize}
- setFontSize={props.setFontSize}
- fontColor={props.fontColor}
- setFontColor={props.setFontColor}
- isResize={props.isResize}
- setIsResize={props.setIsResize}
- isFreeResize={false}
- isOpenSignPad={true}
- calculateFontsize={calculateFontsize}
- currWidgetsDetails={
- props?.currWidgetsDetails
- }
- />
-
- );
- })}
-
- );
- }))}
- {/* large device */}
- {/* this component for render pdf document is in middle of the component */}
- {t("failed-to-load-refresh-page")}}
- onLoadError={() => props.setPdfLoad(false)}
- loading={t("loading-doc")}
- onLoadSuccess={props.pageDetails}
- onClick={() =>
- props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
- }
- file={pdfDataBase64}
- >
- {
- console.log("annotation error", error);
- }}
- />
-
-
-
- )}
+
{
+ console.log("annotation error", error);
+ }}
+ />
+
+
+
>
);
}
diff --git a/apps/OpenSign/src/components/pdf/WidgetComponent.jsx b/apps/OpenSign/src/components/pdf/WidgetComponent.jsx
index 2cc92381d..1e87c60b1 100644
--- a/apps/OpenSign/src/components/pdf/WidgetComponent.jsx
+++ b/apps/OpenSign/src/components/pdf/WidgetComponent.jsx
@@ -8,6 +8,7 @@ import {
isMobile,
radioButtonWidget,
textInputWidget,
+ cellsWidget,
textWidget,
widgets
} from "../../constant/Utils";
@@ -37,6 +38,10 @@ function WidgetComponent(props) {
type: "BOX",
item: { type: "BOX", id: 7, text: textInputWidget }
});
+ const [, cells] = useDrag({
+ type: "BOX",
+ item: { type: "BOX", id: 17, text: cellsWidget }
+ });
const [, initials] = useDrag({
type: "BOX",
item: { type: "BOX", id: 8, text: "initials" }
@@ -89,6 +94,7 @@ function WidgetComponent(props) {
date,
text,
textInput,
+ cells,
checkbox,
dropdown,
radioButton,
@@ -132,7 +138,9 @@ function WidgetComponent(props) {
);
const filterWidgets = widget.filter(
(data) =>
- !["dropdown", radioButtonWidget, textInputWidget].includes(data.type)
+ !["dropdown", radioButtonWidget, textInputWidget].includes(
+ data.type
+ )
);
const textWidgetData = widget.filter((data) => data.type !== textWidget);
const updateWidgets = props.isSignYourself
diff --git a/apps/OpenSign/src/components/pdf/WidgetNameModal.jsx b/apps/OpenSign/src/components/pdf/WidgetNameModal.jsx
index a9779c90e..e25948b49 100644
--- a/apps/OpenSign/src/components/pdf/WidgetNameModal.jsx
+++ b/apps/OpenSign/src/components/pdf/WidgetNameModal.jsx
@@ -5,6 +5,7 @@ import RegexParser from "regex-parser";
import {
signatureTypes,
textInputWidget,
+ cellsWidget,
textWidget
} from "../../constant/Utils";
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
@@ -19,7 +20,8 @@ const WidgetNameModal = (props) => {
status: "required",
hint: "",
textvalidate: "",
- isReadOnly: false
+ isReadOnly: false,
+ cellCount: 5
});
const [isValid, setIsValid] = useState(true);
const statusArr = ["Required", "Optional"];
@@ -51,12 +53,14 @@ const WidgetNameModal = (props) => {
props.defaultdata?.options?.validation?.type === "regex"
? props.defaultdata?.options?.validation?.pattern
: props.defaultdata?.options?.validation?.type || "",
- isReadOnly: props.defaultdata?.options?.isReadOnly || false
+ isReadOnly: props.defaultdata?.options?.isReadOnly || false,
+ cellCount: props.defaultdata?.options?.cellCount || 5
});
} else {
setFormdata({
...formdata,
- name: props.defaultdata?.options?.name || ""
+ name: props.defaultdata?.options?.name || "",
+ cellCount: props.defaultdata?.options?.cellCount || 5
});
}
@@ -83,7 +87,9 @@ const WidgetNameModal = (props) => {
props.handleData(data, props.defaultdata?.type);
}
} else {
- const isTextInput = props.defaultdata?.type === textInputWidget;
+ const isTextInput = [textInputWidget, cellsWidget].includes(
+ props.defaultdata?.type
+ );
const { isReadOnly, defaultValue, status } = formdata;
// If it’s a text‐input widget, enforce that read-only fields have
// either a defaultValue or an "optional" status.
@@ -104,7 +110,8 @@ const WidgetNameModal = (props) => {
defaultValue: "",
status: "required",
hint: "",
- textvalidate: ""
+ textvalidate: "",
+ cellCount: 5
});
setSignatureType(signTypes);
}
@@ -125,7 +132,11 @@ const WidgetNameModal = (props) => {
} else {
setIsValid(true);
}
- setFormdata({ ...formdata, [e.target.name]: e.target.value });
+ const val =
+ props.defaultdata?.type === cellsWidget
+ ? e.target.value.slice(0, formdata.cellCount)
+ : e.target.value;
+ setFormdata({ ...formdata, [e.target.name]: val });
};
function handleValidation(type) {
@@ -136,6 +147,8 @@ const WidgetNameModal = (props) => {
return "/^\\d+$/";
case "text":
return "/^[a-zA-Zs]+$/";
+ case "ssn":
+ return "^\\d{3}-\\d{2}-\\d{4}$";
default:
return type;
}
@@ -163,7 +176,7 @@ const WidgetNameModal = (props) => {
)}
- {props.defaultdata?.type === textInputWidget && (
+ {props.defaultdata?.type === cellsWidget && (
+
+ {t("cell-count")}
+ handleChange(e)}
+ required
+ />
+
+ )}
+ {[textInputWidget, cellsWidget].includes(props.defaultdata?.type) && (
<>
@@ -199,6 +226,11 @@ const WidgetNameModal = (props) => {
value={formdata.defaultValue}
onChange={(e) => handledefaultChange(e)}
autoComplete="off"
+ maxLength={
+ props.defaultdata?.type === cellsWidget
+ ? formdata.cellCount
+ : undefined
+ }
onBlur={() => {
if (isValid === false) {
setFormdata({ ...formdata, defaultValue: "" });
@@ -250,7 +282,9 @@ const WidgetNameModal = (props) => {
);
})}
- {[textInputWidget].includes(props.defaultdata?.type) && (
+ {[textInputWidget, cellsWidget].includes(
+ props.defaultdata?.type
+ ) && (
{
}))
}
/>
-
+
{t("read-only")}
@@ -317,6 +351,7 @@ const WidgetNameModal = (props) => {
{[
textInputWidget,
textWidget,
+ cellsWidget,
"name",
"company",
"job title",
diff --git a/apps/OpenSign/src/components/pdf/WidgetsValueModal.jsx b/apps/OpenSign/src/components/pdf/WidgetsValueModal.jsx
index 44d78d2d7..76d19d596 100644
--- a/apps/OpenSign/src/components/pdf/WidgetsValueModal.jsx
+++ b/apps/OpenSign/src/components/pdf/WidgetsValueModal.jsx
@@ -15,9 +15,11 @@ import {
radioButtonWidget,
selectCheckbox,
textInputWidget,
+ cellsWidget,
textWidget,
years
} from "../../constant/Utils";
+import CellsWidget from "./CellsWidget";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import SignatureCanvas from "react-signature-canvas";
@@ -93,7 +95,6 @@ function WidgetsValueModal(props) {
const [isTab, setIsTab] = useState("");
const [textWidth, setTextWidth] = useState(0);
const [textHeight, setTextHeight] = useState(0);
- const [signatureType, setSignatureType] = useState("");
const [isSignTypes, setIsSignTypes] = useState(true);
const [typedSignature, setTypedSignature] = useState("");
const [selectDate, setSelectDate] = useState({});
@@ -119,13 +120,33 @@ function WidgetsValueModal(props) {
const currentUserName = jsonSender && jsonSender?.name;
const widgetTypeTranslation = t(`widgets-name.${currWidgetsDetails?.type}`);
const [widgetValue, setWidgetValue] = useState(
- currWidgetsDetails?.options?.response ||
- currWidgetsDetails?.options?.defaultValue
+ currWidgetsDetails.type !== "checkbox" &&
+ (currWidgetsDetails?.options?.response ||
+ currWidgetsDetails?.options?.defaultValue)
);
- const [selectedCheckbox, setSelectedCheckbox] = useState(
- currWidgetsDetails?.options?.response ||
+ const [cellsValue, setCellsValue] = useState(() => {
+ const count = currWidgetsDetails?.options?.cellCount || 5;
+ const val =
+ currWidgetsDetails?.options?.response ||
currWidgetsDetails?.options?.defaultValue ||
- []
+ "";
+ return Array.from({ length: count }, (_, i) => val[i] || "");
+ });
+ const cellRefs = useRef([]);
+
+ useEffect(() => {
+ const count = currWidgetsDetails?.options?.cellCount || 5;
+ const val =
+ currWidgetsDetails?.options?.response ||
+ currWidgetsDetails?.options?.defaultValue ||
+ "";
+ setCellsValue(Array.from({ length: count }, (_, i) => val[i] || ""));
+ }, [currWidgetsDetails?.key]);
+ const [selectedCheckbox, setSelectedCheckbox] = useState(
+ currWidgetsDetails.type === "checkbox" &&
+ (currWidgetsDetails?.options?.response ||
+ currWidgetsDetails?.options?.defaultValue ||
+ [])
);
const [startDate, setStartDate] = useState(
currWidgetsDetails?.options?.response
@@ -156,34 +177,6 @@ function WidgetsValueModal(props) {
setHint(currWidgetsDetails?.type);
}
}
- //set already draw or save signature url/text url of signature text type and draw type for initial type and signature type widgets
- if (currWidgetsDetails && canvasRef.current) {
- const isWidgetType = currWidgetsDetails?.type;
- const signatureType = currWidgetsDetails?.signatureType;
- const url = currWidgetsDetails?.SignUrl;
- //checking widget type and draw type signature url
- if (currWidgetsDetails?.type === "initials") {
- if (isWidgetType === "initials" && signatureType === "draw" && url) {
- canvasRef.current.fromDataURL(url);
- }
- } else if (
- isWidgetType === "signature" &&
- signatureType === "draw" &&
- url
- ) {
- canvasRef.current.fromDataURL(url);
- }
-
- const trimmedName = currentUserName && currentUserName?.trim();
- const firstCharacter = trimmedName?.charAt(0);
- const userName =
- currWidgetsDetails?.type === "initials"
- ? firstCharacter
- : currentUserName;
- const signatureValue = currWidgetsDetails?.typeSignature;
- setTypedSignature(signatureValue || userName || "");
- setFontSelect("Fasthand");
- }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currWidgetsDetails]); // Added currWidgetsDetails to dependency array for reset logic
@@ -403,7 +396,6 @@ function WidgetsValueModal(props) {
const tab = signatureTypes?.[getIndex].name;
if (tab === "draw") {
setIsTab("draw");
- setSignatureType("draw");
} else if (tab === "upload") {
setIsImageSelect(true);
setIsTab("uploadImage");
@@ -449,7 +441,32 @@ function WidgetsValueModal(props) {
setTypedSignature("");
}
} else {
- setWidgetValue("");
+ if (currWidgetsDetails?.type === cellsWidget) {
+ const count =
+ currWidgetsDetails?.options?.cellCount || cellsValue.length || 1;
+ const cleared = Array.from({ length: count }, () => "");
+ setCellsValue(cleared);
+ const combined = cleared.join("");
+ setWidgetValue(combined);
+ onChangeInput(
+ combined,
+ currWidgetsDetails?.key,
+ xyPosition,
+ props.index,
+ setXyPosition,
+ uniqueId
+ );
+ } else {
+ setWidgetValue("");
+ onChangeInput(
+ "",
+ currWidgetsDetails?.key,
+ xyPosition,
+ props.index,
+ setXyPosition,
+ uniqueId
+ );
+ }
}
};
//function for set signature url
@@ -583,7 +600,7 @@ function WidgetsValueModal(props) {
if (isTab === "mysignature") {
setSignature("");
if (currWidgetsDetails?.type === "initials") {
- handleSaveSignature(signatureType, "initials");
+ handleSaveSignature(isTab, "initials");
} else {
handleSaveSignature(null, "default");
}
@@ -604,13 +621,13 @@ function WidgetsValueModal(props) {
} else {
setSignature("");
canvasRef?.current?.clear();
- handleSaveSignature(signatureType);
+ handleSaveSignature(isTab);
}
}
setPenColor("blue");
} else {
setSignature("");
- handleSaveImage(signatureType);
+ handleSaveImage();
}
setIsImageSelect(false);
setIsDefaultSign(false);
@@ -649,24 +666,21 @@ function WidgetsValueModal(props) {
}, [fontSelect]);
useEffect(() => {
- if (
- ["signature", "initials"].includes(currWidgetsDetails?.type) &&
- widgetValue
- ) {
- if (isTab === "draw" && currWidgetsDetails?.signatureType === "draw") {
- setSignature(widgetValue);
- } else if (isTab === "uploadImage" && currWidgetsDetails?.ImageType) {
- setImage({ imgType: currWidgetsDetails?.ImageType, src: widgetValue });
+ if (currWidgetsDetails?.options?.response) {
+ const url = currWidgetsDetails?.options?.response;
+ if (["signature", "initials"].includes(currWidgetsDetails?.type)) {
+ if (isTab === "draw" && currWidgetsDetails?.signatureType === "draw") {
+ setSignature(url);
+ // Load the default signature after the component mounts
+ if (canvasRef.current) {
+ canvasRef.current.fromDataURL(url);
+ }
+ } else if (isTab === "uploadImage" && currWidgetsDetails?.ImageType) {
+ setImage({ imgType: currWidgetsDetails?.ImageType, src: url });
+ }
+ } else if (["image", "stamp"].includes(currWidgetsDetails?.type)) {
+ setImage({ imgType: currWidgetsDetails?.ImageType, src: url });
}
- } else if (
- ["image", "stamp"].includes(currWidgetsDetails?.type) &&
- widgetValue
- ) {
- setImage({ imgType: currWidgetsDetails?.ImageType, src: widgetValue });
- }
- // Load the default signature after the component mounts
- if (canvasRef.current) {
- canvasRef.current.fromDataURL(signature);
}
if (isTab === "type") {
const trimmedName = typedSignature
@@ -953,6 +967,65 @@ function WidgetsValueModal(props) {
uniqueId
);
};
+ const handleCellsInput = (e, idx) => {
+ const val = e.target.value.slice(0, 1);
+ const updated = [...cellsValue];
+ updated[idx] = val;
+ setCellsValue(updated);
+ const combined = updated.join("");
+ setWidgetValue(combined);
+ onChangeInput(
+ combined,
+ currWidgetsDetails?.key,
+ xyPosition,
+ props.index,
+ setXyPosition,
+ uniqueId
+ );
+ if (val) {
+ if (idx < cellRefs.current.length - 1) {
+ cellRefs.current[idx + 1]?.focus();
+ }
+ } else if (idx > 0) {
+ // move focus back when a cell becomes empty
+ cellRefs.current[idx - 1]?.focus();
+ }
+ };
+ const handleCellsKeyDown = (e, idx) => {
+ if (e.key === "Backspace" && !cellsValue[idx] && idx > 0) {
+ e.preventDefault();
+ const updated = [...cellsValue];
+ updated[idx - 1] = "";
+ const combined = updated.join("");
+ setCellsValue(updated);
+ setWidgetValue(combined);
+ onChangeInput(
+ combined,
+ currWidgetsDetails?.key,
+ xyPosition,
+ props.index,
+ setXyPosition,
+ uniqueId
+ );
+ cellRefs.current[idx - 1]?.focus();
+ }
+ if (e.key === "Delete") {
+ e.preventDefault();
+ const updated = [...cellsValue];
+ updated[idx] = "";
+ const combined = updated.join("");
+ setCellsValue(updated);
+ setWidgetValue(combined);
+ onChangeInput(
+ combined,
+ currWidgetsDetails?.key,
+ xyPosition,
+ props.index,
+ setXyPosition,
+ uniqueId
+ );
+ }
+ };
//function is used to show widgets on modal according to selected widget type checkbox/date/radio/drodown/textbox/signature/image
const getWidgetType = (type) => {
switch (type) {
@@ -983,7 +1056,6 @@ function WidgetsValueModal(props) {
setIsDefaultSign(true);
setIsImageSelect(true);
setIsTab("mysignature");
- setSignatureType("");
setImage();
}}
className={`${
@@ -1003,7 +1075,6 @@ function WidgetsValueModal(props) {
setIsDefaultSign(true);
setIsImageSelect(true);
setIsTab("mysignature");
- setSignatureType("");
setImage();
}}
className={`${
@@ -1040,7 +1111,6 @@ function WidgetsValueModal(props) {
setIsDefaultSign(false);
setIsImageSelect(true);
setIsTab("uploadImage");
- setSignatureType("");
}}
className={`${
isTab === "uploadImage" && `${isTabCls}`
@@ -1057,7 +1127,6 @@ function WidgetsValueModal(props) {
setIsDefaultSign(false);
setIsImageSelect(false);
setIsTab("type");
- setSignatureType("");
setImage();
}}
className={`${
@@ -1326,69 +1395,66 @@ function WidgetsValueModal(props) {
>
) : (
-
-
-
- {t("signature")}
-
-
- ×
-
-
-
-
{t("at-least-one-signature-type")}
-
+
+
{t("at-least-one-signature-type")}
)}
);
case "checkbox":
+ const checkBoxLayout =
+ currWidgetsDetails?.options?.layout || "vertical";
+ const isMultipleCheckbox =
+ currWidgetsDetails?.options?.values?.length > 0 ? true : false;
+ const checkBoxWrapperClass = `flex items-start ${
+ checkBoxLayout === "horizontal"
+ ? `flex-row flex-wrap ${isMultipleCheckbox ? "gap-x-2" : ""}`
+ : `flex-col ${isMultipleCheckbox ? "gap-y-[5px]" : ""}`
+ }`; // Using gap-y-1 for consistency, adjust if needed
+
return (
-
- {currWidgetsDetails?.options?.values?.map((data, ind) => {
- return (
-
);
case textInputWidget:
@@ -1401,6 +1467,19 @@ function WidgetsValueModal(props) {
className={textInputcls}
/>
);
+ case cellsWidget:
+ return (
+
+ );
case "dropdown":
return (
{currWidgetsDetails?.options?.name}
-
- {currWidgetsDetails?.options?.values?.map((data, ind) => {
- return (
-
- {data}
-
- );
- })}
+ {currWidgetsDetails?.options?.values?.map((data, ind) => (
+
+ {data}
+
+ ))}
);
case "name":
@@ -1529,30 +1601,38 @@ function WidgetsValueModal(props) {
>
);
case radioButtonWidget:
+ const radioLayout = currWidgetsDetails.options?.layout || "vertical";
+ const isOnlyOneBtn =
+ currWidgetsDetails.options?.values?.length > 0 ? true : false;
+ const radioWrapperClass = `flex items-start ${
+ radioLayout === "horizontal"
+ ? `flex-row flex-wrap ${isOnlyOneBtn ? "gap-x-2" : ""}`
+ : `flex-col ${isOnlyOneBtn ? "gap-y-[5px]" : ""}`
+ }`; // Using gap-y-1 for consistency, adjust if needed
return (
-
- {currWidgetsDetails?.options?.values.map((data, ind) => {
- return (
-
-
- {
- handleCheckRadio(e.target.value);
- }}
- />
- {data}
-
-
- );
- })}
+
+ {currWidgetsDetails?.options?.values.map((data, ind) => (
+
+
+ {
+ handleCheckRadio(e.target.value);
+ }}
+ />
+ {data}
+
+
+ ))}
);
case textWidget:
@@ -1590,15 +1670,9 @@ function WidgetsValueModal(props) {
const minCount =
currWidgetsDetails?.options?.validation?.minRequiredCount;
const parseMin = minCount && parseInt(minCount);
- //get maximum required count if exist
- const maxCount =
- currWidgetsDetails?.options?.validation?.maxRequiredCount;
- const parseMax = maxCount && parseInt(maxCount);
- if (parseMin > 0 && parseMax > 0) {
+ if (parseMin > 0) {
isRequired = true;
}
- } else if (isRadio) {
- isRequired = true;
} else {
isRequired = currWidgetsDetails.options?.status === "required";
}
@@ -1799,11 +1873,60 @@ function WidgetsValueModal(props) {
setUniqueId(tempSignerId);
}
};
+ //function is used to excecute on click finish button functionality
const handleFinish = () => {
props?.finishDocument();
dispatch(setIsShowModal({}));
};
+ useEffect(() => {
+ if (!isSave) {
+ handleFinishButton();
+ }
+ }, [isSave]);
+ //'handleFinishButton' function is used to show finish button click on any widge if all required widgtes have response
+ const handleFinishButton = () => {
+ const widgetsPosition = xyPosition?.find((data) => data.Id === uniqueId);
+ //using 'flatMap' create all nested array in one level
+ const editableWidgets = widgetsPosition?.placeHolder?.flatMap((page) =>
+ page.pos
+ .filter((widget) => !widget.options?.isReadOnly)
+ .map((widget) => widget)
+ );
+ const getcurrentwidget = editableWidgets?.find(
+ (data) => data?.key === currWidgetsDetails?.key
+ );
+ if (getcurrentwidget?.options?.response) {
+ props?.setCurrWidgetsDetails(getcurrentwidget);
+ }
+ let isResponse = true;
+ //condition to check all required widgets have response or not then show finish buutton
+ for (const data of editableWidgets) {
+ if (data?.type === "checkbox") {
+ const minCount = data.options?.validation?.minRequiredCount;
+ const parseMin = minCount && parseInt(minCount);
+ const hasNoResponse =
+ (!Array.isArray(data?.options?.response) ||
+ data.options.response.length === 0) &&
+ (!Array.isArray(data?.options?.defaultValue) ||
+ data.options.defaultValue.length === 0);
+ if (parseMin > 0 && hasNoResponse) {
+ isResponse = false;
+ break;
+ }
+ } else if (
+ !data.options.response &&
+ !data?.options?.defaultValue &&
+ data.options?.status === "required"
+ ) {
+ isResponse = false;
+ break;
+ }
+ }
+ if (isResponse) {
+ setIsLastWidget(true);
+ }
+ };
return (
<>
{
};
case textInputWidget:
return { ...status, name: "Text", isReadOnly: false };
+ case cellsWidget:
+ return {
+ ...status,
+ name: "Cells",
+ cellCount: 5,
+ defaultValue: "",
+ validation: { type: "", pattern: "" },
+ isReadOnly: false
+ };
case "initials":
return { ...status, name: "Initials" };
case "name":
@@ -416,6 +427,14 @@ export const addWidgetSelfsignOptions = (type, getWidgetValue, owner) => {
return { name: "Checkbox" };
case textWidget:
return { name: "Text" };
+ case cellsWidget:
+ return {
+ name: "Cells",
+ cellCount: 5,
+ defaultValue: "",
+ validation: { type: "", pattern: "" },
+ isReadOnly: false
+ };
case "initials":
return { name: "Initials" };
case "name":
@@ -486,6 +505,8 @@ export const defaultWidthHeight = (type) => {
return { width: 15, height: 19 };
case textInputWidget:
return { width: 150, height: 19 };
+ case cellsWidget:
+ return { width: 112, height: 22 };
case "dropdown":
return { width: 120, height: 22 };
case "initials":
@@ -685,7 +706,7 @@ export const signPdfFun = async (
export const randomId = () => {
// 1. Grab a cryptographically-secure 32-bit random value
const randomBytes = crypto.getRandomValues(new Uint32Array(1));
- const raw = randomBytes[0]; // 0 … 4 294 967 295
+ const raw = randomBytes[0]; // 0 … 4 294 967 295
// 2. Collapse into a 90 000 000-wide band (0…89 999 999), then shift to 10 000 000…99 999 999
const eightDigit = 10_000_000 + (raw % 90_000_000);
@@ -863,7 +884,8 @@ export const onChangeInput = (
initial,
dateFormat,
fontSize,
- fontColor
+ fontColor,
+ isDateReadOnly
) => {
const isSigners = xyPosition.some((data) => data.signerPtr);
let filterSignerPos;
@@ -892,6 +914,7 @@ export const onChangeInput = (
response: value,
fontSize: fontSize,
fontColor: fontColor,
+ isReadOnly: isDateReadOnly || false,
validation: {
type: "date-format",
format: dateFormat // This indicates the required date format explicitly.
@@ -969,7 +992,6 @@ export const onChangeInput = (
setXyPosition(updatePlaceholder);
}
};
-
//function to increase height of text area on press enter
export const onChangeHeightOfTextArea = (
height,
@@ -1213,8 +1235,8 @@ export function onSaveSign(
}
return obj;
});
- //condition when user click on apply(signature,image,typed signature or defaullt signature) all widgets on signature pad for same widgets
- if (isAutoSign) {
+ //condition when draw/upload signature/initials then apply it all related to widgets (signature,image,typed signature or default signature)
+ if (isApplyAll || isAutoSign) {
const updatedArray = updateXYposition.map((page) => ({
...page,
pos: page.pos.map(
@@ -1233,26 +1255,6 @@ export function onSaveSign(
)
}));
return updatedArray;
- } //condition when user edit signature/initial then updated signature apply all existing drawn signatures
- else if (isApplyAll) {
- const updatedArray = updateXYposition.map((page) => ({
- ...page,
- pos: page.pos.map(
- (item) =>
- item.SignUrl && item.type === widgetsType
- ? {
- ...item,
- Width: posWidth,
- Height: posHeight,
- SignUrl: signatureImg,
- ...(type && { signatureType: type }),
- options: { ...item.options, response: signatureImg },
- ...(typedSignature && { typeSignature: typedSignature })
- }
- : item // Otherwise, keep it unchanged
- )
- }));
- return updatedArray;
} else {
return updateXYposition;
}
@@ -1361,35 +1363,13 @@ export function onSaveImage(
}
return obj;
});
- //condition when user click on apply(stamp) all widgets on signature pad for same widgets
- if (isAutoSign) {
+ //condition when user upload(stamp) then apply it all related to widgets
+ if (isApplyAll || isAutoSign) {
const updatedArray = updateXYposition.map((page) => ({
...page,
pos: page.pos.map(
(item) =>
- item.type === widgetsType
- ? {
- ...item,
- Width: getIMGWH.newWidth,
- Height: getIMGWH.newHeight,
- SignUrl: image.src,
- ImageType: image.imgType,
- options: {
- ...item.options,
- response: image.src
- }
- }
- : item // Otherwise, keep it unchanged
- )
- }));
- return updatedArray;
- } //condition when user edit stamp then updated signature apply all existing drawn signatures
- else if (isApplyAll) {
- const updatedArray = updateXYposition.map((page) => ({
- ...page,
- pos: page.pos.map(
- (item) =>
- item.SignUrl && item.type === widgetsType && item.type !== "image"
+ item.type === widgetsType && item.type !== "image"
? {
...item,
Width: getIMGWH.newWidth,
@@ -1655,6 +1635,7 @@ export const multiSignEmbed = async (
const WidgetsTypeTextExist = [
textWidget,
textInputWidget,
+ cellsWidget,
"name",
"company",
"job title",
@@ -1668,9 +1649,10 @@ export const multiSignEmbed = async (
const color = position?.options?.fontColor;
const updateColorInRgb = getWidgetsFontColor(color);
const fontSize = parseInt(position?.options?.fontSize || 12);
- const widgetTypeExist = [
+ const isTextTypeWidget = [
textWidget,
textInputWidget,
+ cellsWidget,
"name",
"company",
"job title",
@@ -1678,36 +1660,48 @@ export const multiSignEmbed = async (
"email"
].includes(position.type);
if (position.type === "checkbox") {
- let checkboxGapFromTop, isCheck;
- let y = yPos(position) + 2;
- //calculate checkbox size to draw on pdf
- const checkboxSize = fontSize - 1;
- //calculate gap between checkbox and options
- const checkboxTextGapFromLeft = fontSize + 5;
+ // Determine layout mode: 'vertical' (default) or 'horizontal'
+ // const isHorizontal = position.layout === "horizontal";
+ const isHorizontal =
+ position?.options?.layout === "horizontal" ? true : false;
+ // Initial “cursor” positions
+ let currentX = xPos(position);
+ let currentY = yPos(position) + 2;
+ // Size and spacing settings
+ const checkboxSize = fontSize - 1; // checkbox diameter
+ const checkboxTextGapFromLeft = fontSize + 5; // gap between box and its label
+ const verticalGap = fontSize + 3.2; // gap between two rows (vertical layout)
+ let horizontalGap = 0; // will compute after drawing each label
if (position?.options?.values.length > 0) {
- position?.options?.values.forEach((item, ind) => {
- const checkboxRandomId = "checkbox" + randomId();
- if (
- position?.options?.response &&
- position?.options?.response?.length > 0
- ) {
- isCheck = position?.options?.response?.includes(ind);
- } else if (position?.options?.defaultValue) {
- isCheck = position?.options?.defaultValue?.includes(ind);
- }
- const checkbox = form.createCheckBox(checkboxRandomId);
+ position.options.values.forEach((item, ind) => {
+ // 1. Advance the “cursor” on second+ iteration
if (ind > 0) {
- y = y + checkboxGapFromTop;
- } else {
- checkboxGapFromTop = fontSize + 3.2;
+ if (isHorizontal) {
+ currentX += horizontalGap;
+ } else {
+ currentY += verticalGap;
+ }
+ }
+ // 2. Determine whether this checkbox should be checked
+ let isCheck = false;
+ if (
+ position.options.response &&
+ position.options.response.length > 0
+ ) {
+ isCheck = position.options.response.includes(ind);
+ } else if (position.options.defaultValue) {
+ isCheck = position.options.defaultValue.includes(ind);
}
- if (!position?.options?.isHideLabel) {
- // below line of code is used to embed label with radio button in pdf
+ // 3. Draw the label (if labels are not hidden)
+ if (!position.options.isHideLabel) {
+ const labelX = currentX + checkboxTextGapFromLeft;
+ const labelY = currentY - 3;
+
const optionsPosition = compensateRotation(
page.getRotation().angle,
- xPos(position) + checkboxTextGapFromLeft,
- y - 3,
+ labelX,
+ labelY,
1,
getSize,
fontSize,
@@ -1717,98 +1711,134 @@ export const multiSignEmbed = async (
);
page.drawText(item, optionsPosition);
}
+ // 4. Create and place the actual checkbox
+ const checkboxRandomId = "checkbox" + randomId();
+ const checkbox = form.createCheckBox(checkboxRandomId);
let checkboxObj = {
- x: xPos(position),
- y: y,
+ x: currentX,
+ y: currentY,
width: checkboxSize,
height: checkboxSize
};
checkboxObj = getWidgetPosition(page, checkboxObj, 1, getSize);
checkbox.addToPage(page, checkboxObj);
-
- //applied which checkbox should be checked
+ // 5. Check or uncheck as needed, then make read‐only
if (isCheck) {
checkbox.check();
} else {
checkbox.uncheck();
}
checkbox.enableReadOnly();
+ // 6. If horizontal layout, compute how far to shift next checkbox‐circle
+ if (isHorizontal) {
+ // Measure the width of this label text at `fontSize`
+ const textWidth = font.widthOfTextAtSize(item, fontSize);
+ // Next checkbox should come after: [box] + gap + [label text] + extra 10pt padding
+ horizontalGap =
+ checkboxSize + checkboxTextGapFromLeft + textWidth;
+ }
});
}
- } else if (widgetTypeExist) {
- let textContent;
+ } else if (isTextTypeWidget) {
+ let textContent = "";
if (position?.options?.response) {
textContent = position.options?.response;
} else if (position?.options?.defaultValue) {
textContent = position?.options?.defaultValue;
}
- const fixedWidth = widgetWidth; // Set your fixed width
- const isNewOnEnterLineExist = textContent.includes("\n");
-
- // Function to break text into lines based on the fixed width
- const NewbreakTextIntoLines = (textContent, width) => {
- const lines = [];
- let currentLine = "";
-
- for (const word of textContent.split(" ")) {
- //get text line width
- const lineWidth = font.widthOfTextAtSize(
- `${currentLine} ${word}`,
- fontSize
+ if (position.type === cellsWidget) {
+ const cellCount =
+ position?.options?.cellCount || textContent.length || 1;
+ const charWidth = widgetWidth / cellCount;
+ const y = yPos(position) - 4;
+ for (let i = 0; i < cellCount; i++) {
+ const ch = textContent[i] || "";
+ const charX =
+ xPos(position) +
+ charWidth * i +
+ (charWidth - font.widthOfTextAtSize(ch, fontSize)) / 2;
+ const textPosition = compensateRotation(
+ page.getRotation().angle,
+ charX,
+ y,
+ 1,
+ getSize,
+ fontSize,
+ updateColorInRgb,
+ font,
+ page
);
- //check text content line width is less or equal to container width
- if (lineWidth <= width) {
- currentLine += ` ${word}`;
- } else {
- lines.push(currentLine.trim());
- currentLine = `${word}`;
- }
- }
- lines.push(currentLine.trim());
- return lines;
- };
- // Function to break text into lines based on when user go next line on press enter button
- const breakTextIntoLines = (textContent, width) => {
- const lines = [];
- for (const word of textContent.split("\n")) {
- const lineWidth = font.widthOfTextAtSize(`${word}`, fontSize);
- //checking string length to container width
- //if string length is less then container width it means user press enter button
- if (lineWidth <= width) {
- lines.push(word);
- }
- //else adjust text content according to width and send it in new line
- else {
- const newLine = NewbreakTextIntoLines(word, width);
- lines.push(...newLine);
- }
+ if (ch) page.drawText(ch, textPosition);
}
+ } else {
+ const fixedWidth = widgetWidth; // Set your fixed width
+ const isNewOnEnterLineExist = textContent.includes("\n");
- return lines;
- };
- //check if text content have `\n` string it means user press enter to go next line and handle condition
- //else auto adjust text content according to container width
- const lines = isNewOnEnterLineExist
- ? breakTextIntoLines(textContent, fixedWidth)
- : NewbreakTextIntoLines(textContent, fixedWidth);
- // Set initial y-coordinate for the first line
- let x = xPos(position);
- let y = yPos(position) - 4;
- // Embed each line on the page
- for (const line of lines) {
- const textPosition = compensateRotation(
- page.getRotation().angle,
- x,
- y,
- 1,
- getSize,
- fontSize,
- updateColorInRgb,
- font,
- page
- );
- page.drawText(line, textPosition);
- y += 18; // Adjust the line height as needed
+ // Function to break text into lines based on the fixed width
+ const NewbreakTextIntoLines = (textContent, width) => {
+ const lines = [];
+ let currentLine = "";
+
+ for (const word of textContent.split(" ")) {
+ //get text line width
+ const lineWidth = font.widthOfTextAtSize(
+ `${currentLine} ${word}`,
+ fontSize
+ );
+ //check text content line width is less or equal to container width
+ if (lineWidth <= width) {
+ currentLine += ` ${word}`;
+ } else {
+ lines.push(currentLine.trim());
+ currentLine = `${word}`;
+ }
+ }
+ lines.push(currentLine.trim());
+ return lines;
+ };
+ // Function to break text into lines based on when user go next line on press enter button
+ const breakTextIntoLines = (textContent, width) => {
+ const lines = [];
+ for (const word of textContent.split("\n")) {
+ const lineWidth = font.widthOfTextAtSize(`${word}`, fontSize);
+ //checking string length to container width
+ //if string length is less then container width it means user press enter button
+ if (lineWidth <= width) {
+ lines.push(word);
+ }
+ //else adjust text content according to width and send it in new line
+ else {
+ const newLine = NewbreakTextIntoLines(word, width);
+ lines.push(...newLine);
+ }
+ }
+
+ return lines;
+ };
+ //check if text content have `\n` string it means user press enter to go next line and handle condition
+ //else auto adjust text content according to container width
+ const lines = isNewOnEnterLineExist
+ ? breakTextIntoLines(textContent, fixedWidth)
+ : NewbreakTextIntoLines(textContent, fixedWidth);
+ // Set initial y-coordinate for the first line
+ let x = xPos(position);
+ let y = yPos(position) - 4;
+ // Embed each line on the page
+ for (const line of lines) {
+ const textPosition = compensateRotation(
+ page.getRotation().angle,
+ x,
+ y,
+ 1,
+ getSize,
+ fontSize,
+ updateColorInRgb,
+ font,
+ page
+ );
+ page.drawText(line, textPosition);
+ y += 18; // Adjust the line height as needed
+ }
}
} else if (position.type === "dropdown") {
const dropdownRandomId = "dropdown" + randomId();
@@ -1848,33 +1878,47 @@ export const multiSignEmbed = async (
} else if (position.type === radioButtonWidget) {
const radioRandomId = "radio" + randomId();
const radioGroup = form.createRadioGroup(radioRandomId);
- //draw radio button on document and options if hide label is enable
- let radioButtonFromTop;
//getting radio buttons options text font size
- const optionsFontSize = fontSize;
- //calculate value of gap between radio button and options
- const radioTextGapFromLeft = fontSize + 6;
- //getting radio button font size
- const radioSize = fontSize;
- //getting position of radio widget in y direction
- let y = yPos(position);
- //on the basic of option's length create radio button and message
+ const optionsFontSize = fontSize; // font size for option text
+ const radioTextGapFromLeft = fontSize + 6; // gap between circle and its label
+ const radioSize = fontSize; // circle diameter (square of width×height)
+ // Initial “cursor” positions (from your existing helpers)
+ let currentX = xPos(position) + 2;
+ let currentY = yPos(position);
+ // Vertical gap between two radio‐rows
+ const verticalGap = fontSize + 6;
+ // We’ll compute horizontalGap on the fly—after drawing each label
+ // Initialize to zero (will be set after first option is placed)
+ let horizontalGap = 0;
+ // Determine layout mode: 'vertical' or 'horizontal'.
+ // (You mentioned “add one variable called layout” – here we read it from position.layout.)
+ const isHorizontal =
+ position?.options?.layout === "horizontal" ? true : false;
+ // Loop through each option in the group
if (position?.options?.values.length > 0) {
- position?.options?.values.forEach((item, ind) => {
+ position.options.values.forEach((item, ind) => {
+ // 1. Advance cursor on second+ iteration
if (ind > 0) {
- y = y + radioButtonFromTop;
- } else {
- radioButtonFromTop = fontSize + 6;
+ if (isHorizontal) {
+ // Move to the right by horizontalGap
+ currentX += horizontalGap;
+ } else {
+ // Move down by verticalGap (vertical stacking)
+ currentY += verticalGap;
+ }
}
+ // 2. Draw the label text (if not hidden)
if (!position?.options?.isHideLabel) {
- // below line of code is used to embed label with radio button in pdf
+ // Compute where to draw the text (just to the right of the circle)
+ const labelX = currentX + radioTextGapFromLeft;
+ const labelY = currentY - 2;
const optionsPosition = compensateRotation(
page.getRotation().angle,
- xPos(position) + radioTextGapFromLeft,
- y - 2,
+ labelX,
+ labelY,
1,
getSize,
- fontSize,
+ optionsFontSize,
updateColorInRgb,
font,
page
@@ -1882,22 +1926,33 @@ export const multiSignEmbed = async (
page.drawText(item, optionsPosition);
}
+ // 3. Place the radio‐circle itself at (currentX, currentY)
let radioObj = {
- x: xPos(position) + 2,
- y: y,
+ x: currentX,
+ y: currentY,
width: radioSize,
height: radioSize
};
radioObj = getWidgetPosition(page, radioObj, 1, getSize);
radioGroup.addOptionToPage(item, page, radioObj);
+ // 4. If horizontal layout, re-compute horizontalGap for next iteration:
+ if (isHorizontal) {
+ // Measure how wide the label text is, so we know how far to shift next circle
+ const textWidth = font.widthOfTextAtSize(item, optionsFontSize);
+ // radioSize = the circle. radioTextGapFromLeft = gap between circle and label.
+ // Add a small extra padding (e.g. 10pt) before placing next circle.
+ horizontalGap = radioSize + radioTextGapFromLeft + textWidth;
+ }
});
}
+ // 5. Pre‐select a value if provided
if (position?.options?.response) {
radioGroup.select(position.options?.response);
} else if (position?.options?.defaultValue) {
radioGroup.select(position?.options?.defaultValue);
}
+ // 6. Set to read‐only (if required)
radioGroup.enableReadOnly();
} else {
const signature = {
@@ -3180,6 +3235,9 @@ export const checkRegularExpress = (validateType, setValidatePlaceholder) => {
case "text":
setValidatePlaceholder("please enter text");
break;
+ case "ssn":
+ setValidatePlaceholder("123-45-6789");
+ break;
default:
setValidatePlaceholder("please enter value");
}
diff --git a/apps/OpenSign/src/json/ReportJson.js b/apps/OpenSign/src/json/ReportJson.js
index 4efc8cabf..3f09c0a16 100644
--- a/apps/OpenSign/src/json/ReportJson.js
+++ b/apps/OpenSign/src/json/ReportJson.js
@@ -186,6 +186,14 @@ export default function reportJson(id) {
btnIcon: "fa-light fa-envelope",
redirectUrl: "",
action: "saveastemplate"
+ },
+ {
+ btnId: "8440",
+ btnLabel: "Fix & resend",
+ hoverLabel: "Fix & resend",
+ btnIcon: "fa-light fa-paper-plane",
+ redirectUrl: "",
+ action: "recreatedocument"
}
]
}
diff --git a/apps/OpenSign/src/pages/PdfRequestFiles.jsx b/apps/OpenSign/src/pages/PdfRequestFiles.jsx
index f6b09d32e..3a92b49df 100644
--- a/apps/OpenSign/src/pages/PdfRequestFiles.jsx
+++ b/apps/OpenSign/src/pages/PdfRequestFiles.jsx
@@ -47,7 +47,7 @@ import {
mailTemplate,
updateDateWidgetsRes,
widgetDataValue,
- getOriginalWH
+ getOriginalWH,
} from "../constant/Utils";
import Header from "../components/pdf/PdfHeader";
import RenderPdf from "../components/pdf/RenderPdf";
@@ -159,6 +159,9 @@ function PdfRequestFiles(
const [assignedWidgetId, setAssignedWidgetId] = useState([]);
const [showSignPagenumber, setShowSignPagenumber] = useState([]);
const [owner, setOwner] = useState({});
+ const [currentIndex, setCurrentIndex] = useState(0);
+ const [formData, setFormData] = useState([]);
+ const [vacantRole, setVacantRole] = useState();
const [, drop] = useDrop({
accept: "BOX",
drop: (item, monitor) => addPositionOfSignature(item, monitor),
@@ -624,6 +627,7 @@ function PdfRequestFiles(
} catch (err) {
console.log("err in get email verification ", err);
setHandleError(t("something-went-wrong-mssg"));
+ setIsUiLoading(false);
}
}
//check if isEmailVerified then go on next step
@@ -634,7 +638,6 @@ function PdfRequestFiles(
);
if (checkUser && checkUser.length > 0) {
let checkboxExist,
- requiredRadio,
showAlert = false,
widgetKey,
radioExist,
@@ -705,39 +708,12 @@ function PdfRequestFiles(
}
}
}
- //condition to check radio widget exist or not
- else if (radioExist) {
- //get all required type radio button
- requiredRadio = checkUser[0].placeHolder[i].pos.filter(
- (position) =>
- !position.options?.isReadOnly &&
- position.type === radioButtonWidget
- );
- //if required type radio data exit then check user checked all radio button or some radio remain to check
- if (requiredRadio && requiredRadio?.length > 0) {
- let checkSigned;
- for (let i = 0; i < requiredRadio?.length; i++) {
- checkSigned = requiredRadio[i]?.options?.response;
- if (!checkSigned) {
- let checkDefaultSigned =
- requiredRadio[i]?.options?.defaultValue;
- if (!checkDefaultSigned && !showAlert) {
- showAlert = true;
- widgetKey = requiredRadio[i].key;
- TourPageNumber = updatePage;
- setminRequiredCount(null);
- }
- }
- }
- }
- }
- //else condition to check all type widget data fill or not except checkbox and radio button
+ //else condition to check all type widget data fill or not except checkbox
else {
//get all required type widgets except checkbox and radio
const requiredWidgets = checkUser[0].placeHolder[i].pos.filter(
(position) =>
position.options?.status === "required" &&
- position.type !== radioButtonWidget &&
position.type !== "checkbox"
);
if (requiredWidgets && requiredWidgets?.length > 0) {
@@ -745,16 +721,13 @@ function PdfRequestFiles(
for (let i = 0; i < requiredWidgets?.length; i++) {
checkSigned = requiredWidgets[i]?.options?.response;
if (!checkSigned) {
- const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl;
- if (!checkSignUrl) {
- let checkDefaultSigned =
- requiredWidgets[i]?.options?.defaultValue;
- if (!checkDefaultSigned && !showAlert) {
- showAlert = true;
- widgetKey = requiredWidgets[i].key;
- TourPageNumber = updatePage;
- setminRequiredCount(null);
- }
+ let checkDefaultSigned =
+ requiredWidgets[i]?.options?.defaultValue;
+ if (!checkDefaultSigned && !showAlert) {
+ showAlert = true;
+ widgetKey = requiredWidgets[i].key;
+ TourPageNumber = updatePage;
+ setminRequiredCount(null);
}
}
}
@@ -766,20 +739,12 @@ function PdfRequestFiles(
break;
}
}
- if (checkboxExist && requiredCheckbox && showAlert) {
- setUnSignedWidgetId(widgetKey);
- setPageNumber(TourPageNumber);
- setWidgetsTour(true);
- } else if (radioExist && showAlert) {
- setUnSignedWidgetId(widgetKey);
- setPageNumber(TourPageNumber);
- setWidgetsTour(true);
- } else if (showAlert) {
+ if (showAlert) {
setUnSignedWidgetId(widgetKey);
setPageNumber(TourPageNumber);
setWidgetsTour(true);
+ setIsUiLoading(false);
} else {
- setIsUiLoading(true);
// `widgets` is Used to return widgets details with page number of current user
const widgets = checkUser?.[0]?.placeHolder;
let pdfArrBuffer;
@@ -855,16 +820,19 @@ function PdfRequestFiles(
isSuccessRoute,
contactId
);
- const index = pdfDetails?.[0]?.Signers.findIndex(
- (x) => x.objectId === signerObjectId
- );
+ const index =
+ updatedDoc.updatedPdfDetails?.[0]?.Signers.findIndex(
+ (x) => x.objectId === contactId
+ );
const newIndex = index + 1;
const usermail = {
- Email: pdfDetails?.[0]?.Placeholders[newIndex]?.email || ""
+ Email:
+ updatedDoc.updatedPdfDetails?.[0]?.Placeholders[newIndex]
+ ?.email || ""
};
const user = usermail?.Email
? usermail
- : pdfDetails?.[0]?.Signers[newIndex];
+ : updatedDoc.updatedPdfDetails?.[0]?.Signers[newIndex];
if (
sendmail !== "false" &&
sendInOrder
@@ -1023,6 +991,7 @@ function PdfRequestFiles(
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
+ setIsUiLoading(false);
}
} catch (err) {
console.log("err in embedsign", err);
@@ -1036,6 +1005,7 @@ function PdfRequestFiles(
}
const handleSignPdf = async () => {
+ setIsUiLoading(true);
await embedWidgetsData();
};
@@ -1254,7 +1224,8 @@ function PdfRequestFiles(
setRequestSignTour(true);
if (isDontShow) {
const isEnableOTP = pdfDetails?.[0]?.IsEnableOTP || false;
- if (!isEnableOTP) {
+ const sessionToken = localStorage.getItem("accesstoken");
+ if (!isEnableOTP && !sessionToken) {
try {
await axios.post(
`${localStorage.getItem("baseUrl")}functions/updatecontacttour`,
@@ -1651,12 +1622,6 @@ function PdfRequestFiles(
);
setSignerPos(updatesignerPos);
}
-
- // if (dragTypeValue === "dropdown") {
- // setShowDropdown(true);
- // } else if (dragTypeValue === "checkbox") {
- // setIsCheckbox(true);
- // } else
if (
[textWidget, "name", "company", "job title", "email"].includes(
dragTypeValue
@@ -1756,7 +1721,6 @@ function PdfRequestFiles(
setShowSignPagenumber(sortedPagenumber);
}
};
-
return (
{
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
if (filterSignerPos.length > 0) {
@@ -1453,6 +1455,7 @@ function PlaceHolderSign() {
name: dropdownName,
values: dropdownOptions,
status: status,
+ layout: layout,
isReadOnly: isReadOnly || false,
isHideLabel: isHideLabel || false,
defaultValue: defaultValue,
@@ -1492,6 +1495,7 @@ function PlaceHolderSign() {
maxRequiredCount: maxCount
},
defaultValue: defaultValue,
+ layout: layout,
isReadOnly: isReadOnly || false,
isHideLabel: isHideLabel || false,
fontSize:
@@ -1590,6 +1594,36 @@ function PlaceHolderSign() {
isReadOnly: defaultdata?.isReadOnly || false
}
};
+ } else if (position.type === cellsWidget) {
+ return {
+ ...position,
+ options: {
+ ...position.options,
+ name: defaultdata?.name || "Cells",
+ status: defaultdata?.status || "required",
+ hint: defaultdata?.hint || "",
+ cellCount: parseInt(defaultdata?.cellCount || 5),
+ defaultValue: (defaultdata?.defaultValue || "").slice(
+ 0,
+ parseInt(defaultdata?.cellCount || 5)
+ ),
+ validation:
+ isSubscribe && inputype
+ ? {
+ type: inputype,
+ pattern:
+ inputype === "regex" ? defaultdata.textvalidate : ""
+ }
+ : {},
+ fontSize:
+ fontSize || currWidgetsDetails?.options?.fontSize || 12,
+ fontColor:
+ fontColor ||
+ currWidgetsDetails?.options?.fontColor ||
+ "black",
+ isReadOnly: defaultdata?.isReadOnly || false
+ }
+ };
} else if (["signature"].includes(position.type)) {
return {
...position,
diff --git a/apps/OpenSign/src/pages/Report.jsx b/apps/OpenSign/src/pages/Report.jsx
index 6b96481f8..0a3df5690 100644
--- a/apps/OpenSign/src/pages/Report.jsx
+++ b/apps/OpenSign/src/pages/Report.jsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from "react";
+import React, { useEffect, useState, useRef } from "react";
import ReportTable from "../primitives/GetReportDisplay";
import Parse from "parse";
import axios from "axios";
@@ -27,12 +27,17 @@ const Report = () => {
const [isImport, setIsImport] = useState(false);
const abortController = new AbortController();
const docPerPage = 10;
+ const [searchTerm, setSearchTerm] = useState("");
+ const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
+ const debounceTimer = useRef(null);
// below useEffect is call when id param change
useEffect(() => {
setReportName("");
setList([]);
- getReportData();
+ setSearchTerm("");
+ setMobileSearchOpen(false);
+ getReportData(0, docPerPage, "");
// Function returned from useEffect is called on unmount
return () => {
@@ -48,7 +53,7 @@ const Report = () => {
// below useEffect call when isNextRecord state is true and fetch next record
useEffect(() => {
if (isNextRecord) {
- getReportData(List.length, 20);
+ getReportData(List.length, 20, searchTerm);
}
// eslint-disable-next-line
}, [isNextRecord]);
@@ -56,7 +61,46 @@ const Report = () => {
const handleDontShow = (isChecked) => {
setIsDontShow(isChecked);
};
- const getReportData = async (skipUserRecord = 0, limit = 20) => {
+
+ const handleSearchChange = async (e) => {
+ const term = e.target.value.toLowerCase();
+ setSearchTerm(term);
+ if (debounceTimer.current) {
+ clearTimeout(debounceTimer.current);
+ }
+ debounceTimer.current = setTimeout(async () => {
+ try {
+ const headers = {
+ "Content-Type": "application/json",
+ "X-Parse-Application-Id": localStorage.getItem("parseAppId"),
+ sessiontoken: localStorage.getItem("accesstoken"),
+ };
+ const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
+ const res = await axios.post(
+ url,
+ { reportId: id, searchTerm: term, skip: 0, limit: docPerPage },
+ { headers }
+ );
+ const data = res.data?.result || [];
+ if (!data.error) {
+ setList(data);
+ setIsMoreDocs(data.length >= docPerPage);
+ setIsNextRecord(false);
+ }
+ } catch (err) {
+ console.error("Search error:", err);
+ }
+ }, 300);
+ };
+
+ useEffect(() => {
+ return () => {
+ if (debounceTimer.current) {
+ clearTimeout(debounceTimer.current);
+ }
+ };
+ }, []);
+ const getReportData = async (skipUserRecord = 0, limit = 20, term = searchTerm) => {
// setIsLoader(true);
const json = reportJson(id);
if (json) {
@@ -77,6 +121,9 @@ const Report = () => {
const skipRecord = id === "4Hhwbp482K" ? 0 : skipUserRecord;
const limitRecord = id === "4Hhwbp482K" ? 200 : limit;
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
+ if (term) {
+ params.searchTerm = term;
+ }
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
const res = await axios.post(url, params, {
headers: headers,
@@ -197,6 +244,10 @@ const Report = () => {
report_help={reporthelp}
tourData={tourData}
isDontShow={isDontShow}
+ mobileSearchOpen={mobileSearchOpen}
+ setMobileSearchOpen={setMobileSearchOpen}
+ searchTerm={searchTerm}
+ handleSearchChange={handleSearchChange}
isImport={isImport}
/>
) : (
diff --git a/apps/OpenSign/src/pages/SignyourselfPdf.jsx b/apps/OpenSign/src/pages/SignyourselfPdf.jsx
index 3b7a3bae3..59486adfe 100644
--- a/apps/OpenSign/src/pages/SignyourselfPdf.jsx
+++ b/apps/OpenSign/src/pages/SignyourselfPdf.jsx
@@ -22,6 +22,7 @@ import {
randomId,
getDate,
textWidget,
+ cellsWidget,
convertPdfArrayBuffer,
textInputWidget,
fetchImageBase64,
@@ -356,9 +357,13 @@ function SignYourSelf() {
);
const dragTypeValue = item?.text ? item.text : monitor.type;
const widgetValue = getWidgetValue(dragTypeValue);
- const widgetTypeExist = ["name", "company", "job title", "email"].includes(
- dragTypeValue
- );
+ const widgetTypeExist = [
+ "name",
+ "company",
+ "job title",
+ "email",
+ cellsWidget
+ ].includes(dragTypeValue);
const containerScale = getContainerScale(
pdfOriginalWH,
pageNumber,
@@ -439,6 +444,7 @@ function SignYourSelf() {
[
textInputWidget,
textWidget,
+ cellsWidget,
"name",
"company",
"job title",
@@ -931,7 +937,8 @@ function SignYourSelf() {
deleteOption,
status,
defaultValue,
- isHideLabel
+ isHideLabel,
+ layout
) => {
const getPageNumer = xyPosition.filter(
(data) => data.pageNumber === pageNumber
@@ -939,6 +946,8 @@ function SignYourSelf() {
if (getPageNumer.length > 0) {
const getXYdata = getPageNumer[0].pos;
const getPosData = getXYdata;
+ const widgetLayout =
+ currWidgetsDetails?.type === "checkbox" ? { layout: layout } : {};
const addSignPos = getPosData.map((position) => {
if (position.key === currWidgetsDetails?.key) {
if (addOption) {
@@ -962,6 +971,7 @@ function SignYourSelf() {
...position.options,
name: dropdownName,
values: dropdownOptions,
+ ...widgetLayout,
isReadOnly: isReadOnly,
isHideLabel: isHideLabel || false,
fontSize:
@@ -1028,6 +1038,19 @@ function SignYourSelf() {
handleTextSettingModal(false);
}
};
+
+ const setCellCount = (key, newCount) => {
+ const getPageNumer = xyPosition.filter((data) => data.pageNumber === pageNumber);
+ if (getPageNumer.length > 0) {
+ const updatePos = getPageNumer[0].pos.map((p) =>
+ p.key === key ? { ...p, options: { ...p.options, cellCount: newCount } } : p
+ );
+ const updateXYposition = xyPosition.map((obj, ind) =>
+ ind === index ? { ...obj, pos: updatePos } : obj
+ );
+ setXyPosition(updateXYposition);
+ }
+ };
const clickOnZoomIn = () => {
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
};
@@ -1289,6 +1312,7 @@ function SignYourSelf() {
pdfBase64Url={pdfBase64Url}
fontSize={fontSize}
setFontSize={setFontSize}
+ setCellCount={setCellCount}
fontColor={fontColor}
setFontColor={setFontColor}
isResize={isResize}
diff --git a/apps/OpenSign/src/pages/TemplatePlaceholder.jsx b/apps/OpenSign/src/pages/TemplatePlaceholder.jsx
index 85b4e249f..4b033be29 100644
--- a/apps/OpenSign/src/pages/TemplatePlaceholder.jsx
+++ b/apps/OpenSign/src/pages/TemplatePlaceholder.jsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState, useRef } from "react";
+import { useEffect, useState, useRef } from "react";
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import { useParams, useNavigate } from "react-router";
import axios from "axios";
@@ -22,6 +22,7 @@ import {
defaultWidthHeight,
addWidgetOptions,
textInputWidget,
+ cellsWidget,
radioButtonWidget,
getContainerScale,
convertBase64ToFile,
@@ -1274,7 +1275,8 @@ const TemplatePlaceholder = () => {
deleteOption,
status,
defaultValue,
- isHideLabel
+ isHideLabel,
+ layout
) => {
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
if (filterSignerPos.length > 0) {
@@ -1313,6 +1315,7 @@ const TemplatePlaceholder = () => {
name: dropdownName,
values: dropdownOptions,
status: status,
+ layout: layout,
defaultValue: defaultValue,
isReadOnly: isReadOnly || false,
isHideLabel: isHideLabel || false,
@@ -1351,6 +1354,7 @@ const TemplatePlaceholder = () => {
minRequiredCount: minCount,
maxRequiredCount: maxCount
},
+ layout: layout,
isReadOnly: isReadOnly || false,
defaultValue: defaultValue,
isHideLabel: isHideLabel || false,
@@ -1451,6 +1455,36 @@ const TemplatePlaceholder = () => {
"black"
}
};
+ } else if (position.type === cellsWidget) {
+ return {
+ ...position,
+ options: {
+ ...position.options,
+ name: defaultdata?.name || "Cells",
+ status: defaultdata?.status || "required",
+ hint: defaultdata?.hint || "",
+ cellCount: parseInt(defaultdata?.cellCount || 5),
+ defaultValue: (defaultdata?.defaultValue || "").slice(
+ 0,
+ parseInt(defaultdata?.cellCount || 5)
+ ),
+ validation:
+ isSubscribe && inputype
+ ? {
+ type: inputype,
+ pattern:
+ inputype === "regex" ? defaultdata.textvalidate : ""
+ }
+ : {},
+ isReadOnly: defaultdata?.isReadOnly || false,
+ fontSize:
+ fontSize || currWidgetsDetails?.options?.fontSize || 12,
+ fontColor:
+ fontColor ||
+ currWidgetsDetails?.options?.fontColor ||
+ "black"
+ }
+ };
} else if (["signature"].includes(position.type)) {
return {
...position,
@@ -1510,6 +1544,22 @@ const TemplatePlaceholder = () => {
setIsRadio(false);
setIsCheckbox(false);
};
+ const setCellCount = (key, newCount) => {
+ const updated = signerPos.map((signer) => {
+ if (signer.Id !== uniqueId) return signer;
+ const placeHolder = signer.placeHolder.map((ph) => {
+ if (ph.pageNumber !== pageNumber) return ph;
+ const pos = ph.pos.map((p) =>
+ p.key === key
+ ? { ...p, options: { ...p.options, cellCount: newCount } }
+ : p
+ );
+ return { ...ph, pos };
+ });
+ return { ...signer, placeHolder };
+ });
+ setSignerPos(updated);
+ };
const clickOnZoomIn = () => {
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
@@ -1828,6 +1878,7 @@ const TemplatePlaceholder = () => {
pdfBase64Url={pdfBase64Url}
fontSize={fontSize}
setFontSize={setFontSize}
+ setCellCount={setCellCount}
fontColor={fontColor}
setFontColor={setFontColor}
isResize={isResize}
diff --git a/apps/OpenSign/src/primitives/GetReportDisplay.jsx b/apps/OpenSign/src/primitives/GetReportDisplay.jsx
index 8c7b15ee9..c7e30e9d1 100644
--- a/apps/OpenSign/src/primitives/GetReportDisplay.jsx
+++ b/apps/OpenSign/src/primitives/GetReportDisplay.jsx
@@ -7,6 +7,7 @@ import ModalUi from "./ModalUi";
import AddSigner from "../components/AddSigner";
import {
emailRegex,
+ iconColor,
} from "../constant/const";
import Alert from "./Alert";
import Tooltip from "./Tooltip";
@@ -907,7 +908,6 @@ const ReportTable = (props) => {
setActLoader({});
}
};
-
const handleUpdateExpiry = async (e, item) => {
e.preventDefault();
e.stopPropagation();
@@ -1314,7 +1314,6 @@ const ReportTable = (props) => {
try {
const params = { docId: doc?.objectId };
const templateRes = await Parse.Cloud.run("saveastemplate", params);
- // console.log("templateRes ", templateRes);
setTemplateId(templateRes?.id);
setIsSuccess({ [doc.objectId]: true });
} catch (err) {
@@ -1394,6 +1393,12 @@ const ReportTable = (props) => {
setActLoader({});
}
};
+
+ const restrictBtn = (item, act) => {
+ return item.IsSignyourself && act.action === "recreatedocument"
+ ? true
+ : false;
+ };
return (
{Object.keys(actLoader)?.length > 0 && (
@@ -1449,6 +1454,23 @@ const ReportTable = (props) => {
className="cursor-pointer fa-light fa-square-plus text-accent text-[30px] md:text-[35px]"
>
)}
+ {/* Search input */}
+
+
+
+
props.setMobileSearchOpen(!props.mobileSearchOpen)}
+ >
+
+
{
+ {props.mobileSearchOpen && (
+
+
+
+ )}
0
@@ -1837,33 +1870,39 @@ const ReportTable = (props) => {
{isOption[item.objectId] &&
act.action === "option" && (
- {act.subaction?.map((subact) => (
-
- handleActionBtn(
- subact,
- item
- )
- }
- title={t(
- `btnLabel.${subact.hoverLabel}`
- )}
- >
-
-
- {subact.btnLabel && (
-
- {t(
- `btnLabel.${subact.btnLabel}`
+ {act.subaction?.map(
+ (subact) =>
+ !restrictBtn(
+ item,
+ subact
+ ) && (
+
+ handleActionBtn(
+ subact,
+ item
+ )
+ }
+ title={t(
+ `btnLabel.${subact.hoverLabel}`
+ )}
+ >
+
+
+ {subact.btnLabel && (
+
+ {t(
+ `btnLabel.${subact.btnLabel}`
+ )}
+
)}
- )}
-
-
- ))}
+
+ )
+ )}
)}
diff --git a/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js b/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js
index 2d4ce5899..0ac40523f 100644
--- a/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js
+++ b/apps/OpenSignServer/cloud/parsefunction/createBatchDocs.js
@@ -15,8 +15,6 @@ async function deductcount(docsCount, extUserId) {
async function sendMail(document, publicUrl) {
//sessionToken
const baseUrl = new URL(publicUrl);
-
- // console.log("pdfDetails", pdfDetails);
const timeToCompleteDays = document?.TimeToCompleteDays || 15;
const ExpireDate = new Date(document.createdAt);
ExpireDate.setDate(ExpireDate.getDate() + timeToCompleteDays);
@@ -168,9 +166,9 @@ async function batchQuery(userId, Documents, Ip, parseConfig, type, publicUrl) {
})),
ACL: Acl,
SentToOthers: true,
- RemindOnceInEvery: x.RemindOnceInEvery || 5,
+ RemindOnceInEvery: x.RemindOnceInEvery ? parseInt(x.RemindOnceInEvery) : 5,
AutomaticReminders: x.AutomaticReminders || false,
- TimeToCompleteDays: x.TimeToCompleteDays || 15,
+ TimeToCompleteDays: x.TimeToCompleteDays ? parseInt(x.TimeToCompleteDays) : 15,
OriginIp: Ip,
DocSentAt: { __type: 'Date', iso: isoDate },
IsEnableOTP: x?.IsEnableOTP || false,
@@ -231,6 +229,7 @@ export default async function createBatchDocs(request) {
const sessionToken = request.headers?.sessiontoken;
const type = request.headers?.type || 'quicksend';
const Documents = JSON.parse(strDocuments);
+
const Ip = request?.headers?.['x-real-ip'] || '';
// Access the host from the headers
const publicUrl = request.headers.public_url;
diff --git a/apps/OpenSignServer/cloud/parsefunction/getReport.js b/apps/OpenSignServer/cloud/parsefunction/getReport.js
index a55496c6e..85eb6f662 100644
--- a/apps/OpenSignServer/cloud/parsefunction/getReport.js
+++ b/apps/OpenSignServer/cloud/parsefunction/getReport.js
@@ -2,19 +2,26 @@ import { cloudServerUrl } from '../../Utils.js';
import reportJson from './reportsJson.js';
import axios from 'axios';
+// Escape regex special characters. Copied from filterDocs.js
+function escapeRegExp(str) {
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+}
+
export default async function getReport(request) {
const reportId = request.params.reportId;
const limit = request.params.limit;
const skip = request.params.skip;
+ const searchTerm = request.params.searchTerm || '';
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const appId = process.env.APP_ID;
const masterKey = process.env.MASTER_KEY;
+ const sessionToken = request.headers['sessiontoken'] || request.headers['x-parse-session-token'];
try {
const userRes = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': appId,
- 'X-Parse-Session-Token': request.headers['sessiontoken'],
+ 'X-Parse-Session-Token': sessionToken,
},
});
const userId = userRes.data && userRes.data.objectId;
@@ -25,7 +32,7 @@ export default async function getReport(request) {
const { params, keys } = json;
const orderBy = '-updatedAt';
const strKeys = keys.join();
- let strParams = JSON.stringify(params);
+ let paramsObj = { ...params };
if (reportId == '6TeaPr321t') {
const extUserQuery = new Parse.Query('contracts_Users');
extUserQuery.equalTo('Email', userRes.data.email);
@@ -36,8 +43,8 @@ export default async function getReport(request) {
if (_extUser?.TeamIds && _extUser.TeamIds?.length > 0) {
let teamArr = [];
_extUser?.TeamIds?.forEach(x => (teamArr = [...teamArr, ...x.Ancestors]));
- strParams = JSON.stringify({
- ...params,
+ paramsObj = {
+ ...paramsObj,
$or: [
{ SharedWith: { $in: teamArr } },
{
@@ -55,15 +62,23 @@ export default async function getReport(request) {
},
},
],
- });
+ };
} else {
- strParams = JSON.stringify({
- ...params,
+ paramsObj = {
+ ...paramsObj,
CreatedBy: { __type: 'Pointer', className: '_User', objectId: userId },
- });
+ };
}
}
}
+ if (searchTerm) {
+ const escaped = escapeRegExp(searchTerm);
+ paramsObj = {
+ ...paramsObj,
+ Name: { $regex: `.*${escaped}.*`, $options: 'i' },
+ };
+ }
+ const strParams = JSON.stringify(paramsObj);
const headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': appId,
diff --git a/apps/OpenSignServer/cloud/parsefunction/recreateDocument.js b/apps/OpenSignServer/cloud/parsefunction/recreateDocument.js
index 16dbbf7c3..8ddcaf26e 100644
--- a/apps/OpenSignServer/cloud/parsefunction/recreateDocument.js
+++ b/apps/OpenSignServer/cloud/parsefunction/recreateDocument.js
@@ -14,11 +14,14 @@ export default async function recreateDocument(request) {
if (!doc) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found');
}
+ if (doc?.get('IsSignyourself')) {
+ throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Signyourself Document not allowed');
+ }
const _docRes = doc?.toJSON();
const { objectId, SignedUrl, AuditTrail, ACL, DeclineBy, DeclineReason, ...docRes } = _docRes;
const createDoc = new Parse.Object('contracts_Document');
Object.entries(docRes).forEach(([key, value]) => {
- if (key === 'IsDeclined') {
+ if (key === 'IsDeclined' || key === 'IsCompleted') {
createDoc.set(key, false);
} else {
createDoc.set(key, value);
diff --git a/apps/OpenSignServer/cloud/parsefunction/reportsJson.js b/apps/OpenSignServer/cloud/parsefunction/reportsJson.js
index 387387ac8..7bcb1107e 100644
--- a/apps/OpenSignServer/cloud/parsefunction/reportsJson.js
+++ b/apps/OpenSignServer/cloud/parsefunction/reportsJson.js
@@ -123,6 +123,8 @@ export default function reportJson(id, userId) {
'TimeToCompleteDays',
'IsSignyourself',
'IsCompleted',
+ 'ExpiryDate',
+ 'IsSignyourself',
],
};
// declined documents report
diff --git a/apps/OpenSignServer/cloud/parsefunction/sendMailGmailProvider.js b/apps/OpenSignServer/cloud/parsefunction/sendMailGmailProvider.js
index b02905d9c..fb5c85f64 100644
--- a/apps/OpenSignServer/cloud/parsefunction/sendMailGmailProvider.js
+++ b/apps/OpenSignServer/cloud/parsefunction/sendMailGmailProvider.js
@@ -53,10 +53,16 @@ const makeEmail = async (
const isSecure =
new URL(url)?.protocol === 'https:' && new URL(url)?.hostname !== 'localhost';
if (isSecure) {
- https.get(url, async function (response) {
- response.pipe(Pdf);
- response.on('end', () => resolve('success'));
- });
+ https
+ .get(url, async function (response) {
+ response.pipe(Pdf);
+ Pdf.on('finish', () => resolve('success'));
+ Pdf.on('error', () => resolve('error'));
+ })
+ .on('error', e => {
+ console.error(`error: ${e.message}`);
+ resolve('error');
+ });
} else {
const httpsAgent = new https.Agent({ rejectUnauthorized: false }); // Disable SSL validation
axios