Compare commits

...
Author SHA1 Message Date
nxglabs 7e87aa4c38 Merge pull request #790 from nxglabs/fix_readonly
fix: width should be 100% of text type widgets
2025-06-11 17:51:34 +00:00
27 changed files with 1511 additions and 1059 deletions
@@ -361,6 +361,7 @@
"date": "Datum", "date": "Datum",
"text": "Text", "text": "Text",
"text input": "Texteingabe", "text input": "Texteingabe",
"cells": "Zellen",
"checkbox": "Checkbox", "checkbox": "Checkbox",
"dropdown": "Dropdown", "dropdown": "Dropdown",
"radio button": "Radiobutton", "radio button": "Radiobutton",
@@ -415,10 +416,12 @@
"options": "Optionen", "options": "Optionen",
"minimun-check": "Minimale Anzahl", "minimun-check": "Minimale Anzahl",
"maximum-check": "Maximale Anzahl", "maximum-check": "Maximale Anzahl",
"cell-count": "Zellzahl",
"default-value": "Standardwert", "default-value": "Standardwert",
"select": "Auswählen", "select": "Auswählen",
"read-only": "Nur lesen", "read-only": "Ist schreibgeschützt",
"hide-labels": "Labels ausblenden", "hide-labels": "Labels ausblenden",
"layout": "Layout",
"checkbox": "Checkbox", "checkbox": "Checkbox",
"alert": "Warnung", "alert": "Warnung",
"zoom-in": "Vergrößern", "zoom-in": "Vergrößern",
@@ -1025,5 +1028,6 @@
"not-found-in-signature": "Nicht in Signatur gefunden", "not-found-in-signature": "Nicht in Signatur gefunden",
"readonly-textinput-error": "Schreibgeschütztes Text-Widget muss einen Standardwert haben oder optional sein.", "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-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"
} }
@@ -361,6 +361,7 @@
"date": "date", "date": "date",
"text": "text", "text": "text",
"text input": "text input", "text input": "text input",
"cells": "cells",
"checkbox": "checkbox", "checkbox": "checkbox",
"dropdown": "dropdown", "dropdown": "dropdown",
"radio button": "radio button", "radio button": "radio button",
@@ -415,10 +416,12 @@
"options": "Options", "options": "Options",
"minimun-check": "Minimun check", "minimun-check": "Minimun check",
"maximum-check": "Maximum check", "maximum-check": "Maximum check",
"cell-count": "Cell count",
"default-value": "Default value", "default-value": "Default value",
"select": "Select", "select": "Select",
"read-only": "Is read only", "read-only": "read only",
"hide-labels": "Hide labels", "hide-labels": "Hide labels",
"layout": "Layout",
"checkbox": "Checkbox", "checkbox": "Checkbox",
"alert": "Alert", "alert": "Alert",
"zoom-in": "Zoom in", "zoom-in": "Zoom in",
@@ -1025,5 +1028,6 @@
"not-found-in-signature": "Not found in signature", "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-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-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"
} }
@@ -362,6 +362,7 @@
"date": "fecha", "date": "fecha",
"text": "texto", "text": "texto",
"text input": "entrada de texto", "text input": "entrada de texto",
"cells": "células",
"checkbox": "casilla", "checkbox": "casilla",
"dropdown": "desplegable", "dropdown": "desplegable",
"radio button": "botón de radio", "radio button": "botón de radio",
@@ -416,10 +417,12 @@
"options": "Opciones", "options": "Opciones",
"minimun-check": "Chequeo mínimo", "minimun-check": "Chequeo mínimo",
"maximum-check": "Chequeo máximo", "maximum-check": "Chequeo máximo",
"cell-count": "recuento de células",
"default-value": "Valor por defecto", "default-value": "Valor por defecto",
"select": "Seleccionar", "select": "Seleccionar",
"read-only": "Es de solo lectura", "read-only": "Es de solo lectura",
"hide-labels": "Esconder etiquetas", "hide-labels": "Esconder etiquetas",
"layout": "Diseño",
"checkbox": "Casilla", "checkbox": "Casilla",
"alert": "Alerta", "alert": "Alerta",
"zoom-in": "Acercar", "zoom-in": "Acercar",
@@ -1025,5 +1028,6 @@
"not-found-in-signature": "No encontrado en la firma", "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-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-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"
} }
@@ -361,6 +361,7 @@
"date": "date", "date": "date",
"text": "texte", "text": "texte",
"text input": "saisie de texte", "text input": "saisie de texte",
"cells": "cellules",
"checkbox": "case à cocher", "checkbox": "case à cocher",
"dropdown": "dérouler", "dropdown": "dérouler",
"radio button": "bouton radio", "radio button": "bouton radio",
@@ -415,10 +416,12 @@
"options": "Possibilités", "options": "Possibilités",
"minimun-check": "Vérification minimale", "minimun-check": "Vérification minimale",
"maximum-check": "Contrôle maximum", "maximum-check": "Contrôle maximum",
"cell-count": "numération cellulaire",
"default-value": "Valeur par défaut", "default-value": "Valeur par défaut",
"select": "Sélectionner", "select": "Sélectionner",
"read-only": "Est en lecture seule", "read-only": "Est en lecture seule",
"hide-labels": "Masquer les étiquettes", "hide-labels": "Masquer les étiquettes",
"layout": "Disposition",
"checkbox": "Case à cocher", "checkbox": "Case à cocher",
"alert": "Alerte", "alert": "Alerte",
"zoom-in": "Agrandir", "zoom-in": "Agrandir",
@@ -1025,5 +1028,6 @@
"not-found-in-signature": "Introuvable dans la signature", "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-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-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"
} }
@@ -361,6 +361,7 @@
"date": "दिनांक", "date": "दिनांक",
"text": "पाठ", "text": "पाठ",
"text input": "पाठ इनपुट", "text input": "पाठ इनपुट",
"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": "सेल्स संख्या",
"default-value": "डिफ़ॉल्ट मान", "default-value": "डिफ़ॉल्ट मान",
"select": "चुनें", "select": "चुनें",
"read-only": "केवल पढ़ने के लिए है", "read-only": "सिर्फ पढ़ने के लिए है",
"hide-labels": "लेबल छिपाएँ", "hide-labels": "लेबल छिपाएँ",
"layout": "लेआउट",
"checkbox": "चेकबॉक्स", "checkbox": "चेकबॉक्स",
"alert": "चेतावनी", "alert": "चेतावनी",
"zoom-in": "ज़ूम इन करें", "zoom-in": "ज़ूम इन करें",
@@ -1025,5 +1028,6 @@
"not-found-in-signature": "हस्ताक्षर में नहीं मिला", "not-found-in-signature": "हस्ताक्षर में नहीं मिला",
"readonly-textinput-error": "रीड-ओनली टेक्स्ट विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।", "readonly-textinput-error": "रीड-ओनली टेक्स्ट विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।",
"readonly-dropdown-error": "रीड-ओनली ड्रॉपडाउन विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।", "readonly-dropdown-error": "रीड-ओनली ड्रॉपडाउन विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।",
"readonly-radiobtn-error": "रीड-ओनली रेडियो बटन विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।" "readonly-radiobtn-error": "रीड-ओनली रेडियो बटन विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।",
"choose-one":"एक का चयन"
} }
@@ -361,6 +361,7 @@
"date": "data", "date": "data",
"text": "testo", "text": "testo",
"text input": "campo di testo", "text input": "campo di testo",
"cells": "cellule",
"checkbox": "casella di controllo", "checkbox": "casella di controllo",
"dropdown": "menu a tendina", "dropdown": "menu a tendina",
"radio button": "pulsante di opzione", "radio button": "pulsante di opzione",
@@ -415,10 +416,12 @@
"options": "Opzioni", "options": "Opzioni",
"minimun-check": "Controllo minimo", "minimun-check": "Controllo minimo",
"maximum-check": "Controllo massimo", "maximum-check": "Controllo massimo",
"cell-count": "conteggio delle cellule",
"default-value": "Valore predefinita", "default-value": "Valore predefinita",
"select": "Seleziona", "select": "Seleziona",
"read-only": "È solo lettura", "read-only": di sola lettura",
"hide-labels": "Nascondi etichette", "hide-labels": "Nascondi etichette",
"layout": "Layout",
"checkbox": "Casella di controllo", "checkbox": "Casella di controllo",
"alert": "Avviso", "alert": "Avviso",
"zoom-in": "Ingrandisci", "zoom-in": "Ingrandisci",
@@ -1025,5 +1028,6 @@
"not-found-in-signature": "Non trovato nella firma", "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-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-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"
} }
@@ -0,0 +1,123 @@
import { useState, useEffect, useRef } from "react";
const Cell = ({
count,
h,
value,
editable,
onChange,
onKeyDown,
inputRef,
index
}) => (
<div
className="flex items-center justify-center border border-gray-800 bg-white"
style={{ flex: `0 0 ${100 / count}%`, height: h }}
>
<input
maxLength={1}
value={value}
readOnly={!editable}
ref={inputRef}
onChange={editable ? (e) => 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" }}
/>
</div>
);
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 (
<div
className="relative flex w-full h-full overflow-visible"
style={{ height }}
>
{resizable && (
<div
className="cell-size-handle absolute left-1/2 -translate-x-1/2 -top-3 cursor-ew-resize touch-none"
onPointerDown={onTopHandlePointerDown}
>
<svg
className="w-4 h-4 text-blue-600"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="m9.69 18.933.003.001C9.89 19.02 10 19 10 19s.11.02.308-.066l.002-.001.006-.003.018-.008a5.741 5.741 0 0 0 .281-.14c.186-.096.446-.24.757-.433.62-.384 1.445-.966 2.274-1.765C15.302 14.988 17 12.493 17 9A7 7 0 1 0 3 9c0 3.492 1.698 5.988 3.355 7.584a13.731 13.731 0 0 0 2.273 1.765 11.842 11.842 0 0 0 .976.544l.062.029.018.008.006.003ZM10 11.25a2.25 2.25 0 1 0 0-4.5 2.25 2.25 0 0 0 0 4.5Z"
clipRule="evenodd"
/>
</svg>
</div>
)}
{cells.map((val, i) => (
<Cell
key={i}
count={cellCount}
h={height}
value={val}
editable={editable}
onChange={onChange}
onKeyDown={onKeyDown}
inputRef={inputRefs ? (el) => (inputRefs.current[i] = el) : undefined}
index={i}
/>
))}
</div>
);
}
@@ -7,8 +7,8 @@ import { fontColorArr, fontsizeArr } from "../../constant/Utils";
function DropdownWidgetOption(props) { function DropdownWidgetOption(props) {
const { t } = useTranslation(); const { t } = useTranslation();
const [dropdownOptionList, setDropdownOptionList] = useState([ const [dropdownOptionList, setDropdownOptionList] = useState([
"option-1", "Option-1",
"option-2" "Option-2"
]); ]);
const [minCount, setMinCount] = useState(0); const [minCount, setMinCount] = useState(0);
const [maxCount, setMaxCount] = useState(0); const [maxCount, setMaxCount] = useState(0);
@@ -17,11 +17,13 @@ function DropdownWidgetOption(props) {
const [isHideLabel, setIsHideLabel] = useState(false); const [isHideLabel, setIsHideLabel] = useState(false);
const [status, setStatus] = useState("required"); const [status, setStatus] = useState("required");
const [defaultValue, setDefaultValue] = useState(""); const [defaultValue, setDefaultValue] = useState("");
const statusArr = ["required", "optional"];
const [defaultCheckbox, setDefaultCheckbox] = useState([]); const [defaultCheckbox, setDefaultCheckbox] = useState([]);
const [layout, setLayout] = useState("vertical");
const statusArr = ["required", "optional"];
const layoutArr = ["vertical", "horizontal"];
const resetState = () => { const resetState = () => {
setDropdownOptionList(["option-1", "option-2"]); setDropdownOptionList(["Option-1", "Option-2"]);
setDropdownName(props.currWidgetsDetails?.options?.name || props.type); setDropdownName(props.currWidgetsDetails?.options?.name || props.type);
setIsReadOnly(false); setIsReadOnly(false);
setIsHideLabel(false); setIsHideLabel(false);
@@ -29,6 +31,7 @@ function DropdownWidgetOption(props) {
setMaxCount(0); setMaxCount(0);
setDefaultCheckbox([]); setDefaultCheckbox([]);
setDefaultValue(""); setDefaultValue("");
setLayout("vertical");
}; };
useEffect(() => { useEffect(() => {
if ( if (
@@ -48,6 +51,7 @@ function DropdownWidgetOption(props) {
setStatus(props.currWidgetsDetails?.options?.status || "required"); setStatus(props.currWidgetsDetails?.options?.status || "required");
setDefaultValue(props.currWidgetsDetails?.options?.defaultValue || ""); setDefaultValue(props.currWidgetsDetails?.options?.defaultValue || "");
setDefaultCheckbox(props.currWidgetsDetails?.options?.defaultValue || []); setDefaultCheckbox(props.currWidgetsDetails?.options?.defaultValue || []);
setLayout(props.currWidgetsDetails?.options?.layout || "vertical");
} else { } else {
setStatus("required"); setStatus("required");
resetState(); resetState();
@@ -107,6 +111,9 @@ function DropdownWidgetOption(props) {
props?.type === "dropdown" || props?.type === radioButtonWidget; props?.type === "dropdown" || props?.type === radioButtonWidget;
const readOnlyWithoutValue = const readOnlyWithoutValue =
isReadOnly && !defaultValue && status !== "optional"; isReadOnly && !defaultValue && status !== "optional";
const WidgetLayout = ["checkbox", radioButtonWidget].includes(props.type)
? layout
: null;
// If its a dropdown and its read-only without a value (nor marked optional), stop here. // If its a dropdown and its read-only without a value (nor marked optional), stop here.
if (isDropdownOrRadio && readOnlyWithoutValue) { if (isDropdownOrRadio && readOnlyWithoutValue) {
@@ -129,7 +136,8 @@ function DropdownWidgetOption(props) {
null, null,
status, status,
defaultData, defaultData,
isHideLabel isHideLabel,
WidgetLayout
); );
resetState(); resetState();
}; };
@@ -153,17 +161,18 @@ function DropdownWidgetOption(props) {
}} }}
> >
<div> <div>
<label className="text-[13px] font-semibold"> <label htmlFor="title" className="text-[13px] font-semibold">
{t("name")} {t("name")}
<span className="text-[red] text-[13px]"> *</span> <span className="text-[red] text-[13px]"> *</span>
</label> </label>
<input <input
id="title"
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))} onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")} onInput={(e) => e.target.setCustomValidity("")}
required
value={dropdownName} value={dropdownName}
onChange={(e) => setDropdownName(e.target.value)} 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" className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
required
/> />
<label className="text-[13px] font-semibold mt-[5px]"> <label className="text-[13px] font-semibold mt-[5px]">
@@ -245,27 +254,23 @@ function DropdownWidgetOption(props) {
</> </>
)} )}
{props.type !== "checkbox" && ( {props.type !== "checkbox" && (
<> <div className="flex flex-row gap-[10px] mt-[0.5rem]">
<div className="flex flex-row gap-[10px] mt-[0.5rem]"> {statusArr.map((data, ind) => (
{statusArr.map((data, ind) => { <div
return ( key={ind}
<div className="flex flex-row gap-[5px] items-center"
key={ind} >
className="flex flex-row gap-[5px] items-center" <input
> className="op-radio op-radio-xs my-1"
<input type="radio"
className="op-radio op-radio-xs my-1" name="status"
type="radio" onChange={() => setStatus(data.toLowerCase())}
name="status" checked={status.toLowerCase() === data.toLowerCase()}
onChange={() => setStatus(data.toLowerCase())} />
checked={status.toLowerCase() === data.toLowerCase()} <div className="text-[13px] font-500 capitalize">{data}</div>
/> </div>
<div className="text-[13px] font-500">{data}</div> ))}
</div> </div>
);
})}
</div>
</>
)} )}
<div className="flex items-center mt-3 mb-3"> <div className="flex items-center mt-3 mb-3">
<span>{t("font-size")} :</span> <span>{t("font-size")} :</span>
@@ -286,8 +291,8 @@ function DropdownWidgetOption(props) {
); );
})} })}
</select> </select>
<div className="flex flex-row gap-1 items-center ml-4 "> <div className="flex flex-row gap-1 items-center ml-4">
<span>{t("color")} : </span> <span className="capitalize">{t("color")} : </span>
<select <select
value={ value={
props.fontColor || props.fontColor ||
@@ -329,7 +334,7 @@ function DropdownWidgetOption(props) {
className="op-checkbox op-checkbox-sm" className="op-checkbox op-checkbox-sm"
onChange={(e) => setIsReadOnly(e.target.checked)} onChange={(e) => setIsReadOnly(e.target.checked)}
/> />
<label className="ml-1 mb-0" htmlFor="isreadonly"> <label className="ml-2 mb-0 capitalize" htmlFor="isreadonly">
{t("read-only")} {t("read-only")}
</label> </label>
</div> </div>
@@ -344,15 +349,42 @@ function DropdownWidgetOption(props) {
onChange={(e) => setIsHideLabel(e.target.checked)} onChange={(e) => setIsHideLabel(e.target.checked)}
/> />
<label className="ml-1 mb-0" htmlFor="ishidelabel"> <label className="ml-2 mb-0 capitalize" htmlFor="ishidelabel">
{t("hide-labels")} {t("hide-labels")}
</label> </label>
</div> </div>
)} )}
</div> </div>
)} )}
{["checkbox", radioButtonWidget].includes(props.type) && (
<>
<div className="text-[13px] font-semibold mt-[5px] capitalize">
{t("layout")}
</div>
<div
className={`${props.type === "checkbox" ? "mb-[10px]" : ""} flex flex-row gap-[10px] mt-[0.5rem]`}
>
{layoutArr.map((data, ind) => (
<div
key={ind}
className="flex flex-row gap-[5px] items-center"
>
<input
className="op-radio op-radio-xs my-1"
type="radio"
name="layout"
checked={layout.toLowerCase() === data.toLowerCase()}
onChange={() => setLayout(data.toLowerCase())}
/>
<label className="text-[13px] font-500 mb-0 capitalize">
{data}
</label>
</div>
))}
</div>
</>
)}
</div> </div>
<div <div
className={`${ className={`${
props.type === "checkbox" && props.isShowAdvanceFeature props.type === "checkbox" && props.isShowAdvanceFeature
@@ -13,6 +13,7 @@ import {
onChangeInput, onChangeInput,
radioButtonWidget, radioButtonWidget,
textInputWidget, textInputWidget,
cellsWidget,
textWidget textWidget
} from "../../constant/Utils"; } from "../../constant/Utils";
import PlaceholderType from "./PlaceholderType"; import PlaceholderType from "./PlaceholderType";
@@ -85,6 +86,9 @@ function Placeholder(props) {
const [selectDate, setSelectDate] = useState({}); const [selectDate, setSelectDate] = useState({});
const [dateFormat, setDateFormat] = useState([]); const [dateFormat, setDateFormat] = useState([]);
const [clickonWidget, setClickonWidget] = useState({}); const [clickonWidget, setClickonWidget] = useState({});
const [isDateReadOnly, setIsDateReadOnly] = useState(
props?.pos?.options?.isReadOnly || false
);
const startDate = props?.pos?.options?.response const startDate = props?.pos?.options?.response
? getDefaultDate( ? getDefaultDate(
props?.pos?.options?.response, props?.pos?.options?.response,
@@ -406,10 +410,47 @@ function Placeholder(props) {
false, false,
data?.format, data?.format,
props.fontSize || props.pos?.options?.fontSize || 12, props.fontSize || props.pos?.options?.fontSize || 12,
props.fontColor || props.pos?.options?.fontColor || "black" props.fontColor || props.pos?.options?.fontColor || "black",
isDateReadOnly || false
); );
setSelectDate({ date: date, format: data?.format }); setSelectDate({ date: date, format: data?.format });
}; };
const setCellCount = (key, newCount) => {
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 = () => { const PlaceholderIcon = () => {
// 1- If props.isShowBorder is true, display border's icon for all widgets. OR // 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: // 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} id={props.pos.key}
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""} data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
key={props.pos.key} key={props.pos.key}
cancel=".cell-size-handle"
lockAspectRatio={ lockAspectRatio={
!props.isFreeResize && !props.isFreeResize &&
![
textWidget,
"email",
"name",
"company",
"job title",
textInputWidget
].includes(props.pos.type) &&
(props.pos.Width (props.pos.Width
? props.pos.Width / props.pos.Height ? props.pos.Width / props.pos.Height
: defaultWidthHeight(props.pos.type).width / : defaultWidthHeight(props.pos.type).width /
@@ -820,8 +854,10 @@ function Placeholder(props) {
: props.posHeight(props.pos, props.isSignYourself) : props.posHeight(props.pos, props.isSignYourself)
}} }}
minHeight={ minHeight={
props.pos.type !== "checkbox" && props.pos.type === cellsWidget
calculateFont(props.pos.options?.fontSize, true) ? calculateFont(props.pos.options?.fontSize, true)
: props.pos.type !== "checkbox" &&
calculateFont(props.pos.options?.fontSize, true)
} }
maxHeight="auto" maxHeight="auto"
onResizeStart={() => { onResizeStart={() => {
@@ -938,6 +974,7 @@ function Placeholder(props) {
handleSaveDate={handleSaveDate} handleSaveDate={handleSaveDate}
xPos={props.xPos} xPos={props.xPos}
calculateFont={calculateFont} calculateFont={calculateFont}
setCellCount={setCellCount}
/> />
</div> </div>
</Rnd> </Rnd>
@@ -945,11 +982,11 @@ function Placeholder(props) {
<ModalUi isOpen={isDateModal} title={t("widget-info")} showClose={false}> <ModalUi isOpen={isDateModal} title={t("widget-info")} showClose={false}>
<div className="h-[100%] p-[20px]"> <div className="h-[100%] p-[20px]">
<div className="flex flex-row items-center"> <div className="flex flex-col md:flex-row md:items-center gap-y-3">
<span>{t("format")} : </span> <div className="flex flex-row items-center gap-x-1">
<div className="flex"> <span className="capitalize">{t("format")} :</span>
<select <select
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs" className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
defaultValue={""} defaultValue={""}
onChange={(e) => { onChange={(e) => {
const selectedIndex = e.target.value; const selectedIndex = e.target.value;
@@ -973,23 +1010,23 @@ function Placeholder(props) {
{selectDate.format} {selectDate.format}
</span> </span>
</div> </div>
<div className="flex items-center mt-4 md:mt-2"> <div className="flex flex-col md:flex-row gap-y-2 md:gap-y-0 gap-x-2 mt-3">
<span>{t("font-size")} :</span> <div className="flex flex-row items-center">
<select <span className="capitalize">{t("font-size")} :</span>
className="ml-[3px] md:ml:[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs" <select
value={props.fontSize || clickonWidget.options?.fontSize || 12} className="ml-[3px] md:ml:[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
onChange={(e) => props.setFontSize(parseInt(e.target.value))} value={props.fontSize || clickonWidget.options?.fontSize || 12}
> onChange={(e) => props.setFontSize(parseInt(e.target.value))}
{fontsizeArr.map((size, ind) => { >
return ( {fontsizeArr.map((size, ind) => (
<option className="text-[13px]" value={size} key={ind}> <option className="text-[13px]" value={size} key={ind}>
{size} {size}
</option> </option>
); ))}
})} </select>
</select> </div>
<div className="flex flex-row gap-1 items-center ml-2 md:ml-4 "> <div className="flex flex-row gap-1 items-center">
<span>{t("color")}: </span> <span className="capitalize">{t("color")} :</span>
<select <select
value={ value={
props.fontColor || clickonWidget.options?.fontColor || "black" props.fontColor || clickonWidget.options?.fontColor || "black"
@@ -997,13 +1034,11 @@ function Placeholder(props) {
onChange={(e) => props.setFontColor(e.target.value)} onChange={(e) => 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" 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) => { {fontColorArr.map((color, ind) => (
return ( <option value={color} key={ind}>
<option value={color} key={ind}> {t(`color-type.${color}`)}
{t(`color-type.${color}`)} </option>
</option> ))}
);
})}
</select> </select>
<span <span
style={{ style={{
@@ -1014,7 +1049,26 @@ function Placeholder(props) {
></span> ></span>
</div> </div>
</div> </div>
{props?.isPlaceholder && (
<div className="flex items-center mt-3">
<input
id="isReadOnly"
name="isReadOnly"
type="checkbox"
checked={
isDateReadOnly || props.pos.options?.isReadOnly || false
}
className="op-checkbox op-checkbox-xs"
onChange={() => setIsDateReadOnly(!isDateReadOnly)}
/>
<label
className="ml-1.5 mb-0 capitalize text-[13px]"
htmlFor="isreadonly"
>
{t("read-only")}
</label>
</div>
)}
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div> <div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
<button <button
type="button" type="button"
@@ -4,6 +4,7 @@ import {
getYear, getYear,
radioButtonWidget, radioButtonWidget,
textInputWidget, textInputWidget,
cellsWidget,
textWidget, textWidget,
months, months,
years, years,
@@ -14,8 +15,9 @@ import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css"; import "react-datepicker/dist/react-datepicker.css";
import "../../styles/signature.css"; import "../../styles/signature.css";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import CellsWidget from "./CellsWidget";
const textWidgetCls = const textWidgetCls =
"w-full h-full md:min-w-full md:min-h-full z-[999] text-[12px] rounded-[2px] border-[1px] border-[#007bff] overflow-hidden resize-none outline-none text-base-content item-center whitespace-pre-wrap bg-white"; "w-full h-full md:min-w-full md:min-h-full z-[999] text-[12px] rounded-[2px] border-[1px] border-[#007bff] overflow-hidden resize-none outline-none text-base-content item-center whitespace-pre-wrap";
const selectWidgetCls = const selectWidgetCls =
"w-full h-full absolute left-0 top-0 border-[1px] border-[#007bff] rounded-[2px] focus:outline-none text-base-content"; "w-full h-full absolute left-0 top-0 border-[1px] border-[#007bff] rounded-[2px] focus:outline-none text-base-content";
const widgetCls = const widgetCls =
@@ -27,8 +29,12 @@ function PlaceholderType(props) {
props.isSignYourself || props.isSignYourself ||
((props.isSelfSign || props.isNeedSign) && ((props.isSelfSign || props.isNeedSign) &&
props.data?.signerObjId === props.signerObjId); props.data?.signerObjId === props.signerObjId);
const isReadOnly =
props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId;
// prefer the latest response value over any default value
const widgetData = const widgetData =
props.pos?.options?.defaultValue || props.pos?.options?.response; props.pos?.options?.response ?? props.pos?.options?.defaultValue ?? "";
const widgetTypeTranslation = t(`widgets-name.${props?.pos?.type}`); const widgetTypeTranslation = t(`widgets-name.${props?.pos?.type}`);
const inputRef = useRef(null); const inputRef = useRef(null);
const [widgetValue, setwidgetValue] = useState(); const [widgetValue, setwidgetValue] = useState();
@@ -56,16 +62,13 @@ function PlaceholderType(props) {
[] []
); );
} else { } else {
if (widgetData) { // keep displayed value in sync with the stored response
setwidgetValue(widgetData); setwidgetValue(widgetData);
}
} }
if (props.pos?.options?.hint) { if (props.pos?.options?.hint) {
setHint(props.pos?.options.hint); setHint(props.pos?.options.hint);
} else if (props.pos?.options?.validation?.type) { } else if (props.pos?.options?.validation?.type) {
checkRegularExpress(props.pos?.options?.validation?.type, setHint); checkRegularExpress(props.pos?.options?.validation?.type, setHint);
} else {
setHint(props.pos?.type);
} }
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -77,7 +80,8 @@ function PlaceholderType(props) {
color: fontColor, color: fontColor,
fontFamily: "Arial, sans-serif" fontFamily: "Arial, sans-serif"
}} }}
className={`${selectWidgetCls} overflow-hidden`} className={`${isReadOnly ? `select-none` : ``} ${selectWidgetCls} overflow-hidden`}
disabled={isReadOnly}
onClick={onClick} onClick={onClick}
ref={ref} ref={ref}
> >
@@ -148,41 +152,42 @@ function PlaceholderType(props) {
</div> </div>
); );
case "checkbox": 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 ( return (
<div style={{ zIndex: props.isSignYourself && "99" }}> <div
{props.pos.options?.values?.map((data, ind) => { className={checkBoxWrapperClass}
return ( style={{ zIndex: props.isSignYourself && "99" }}
<div key={ind} className="select-none-cls pointer-events-none"> >
<label {props.pos.options?.values?.map((data, ind) => (
htmlFor={`checkbox-${props.pos.key + ind}`} <div key={ind} className="select-none-cls pointer-events-none">
style={{ fontSize: fontSize, color: fontColor }} <label
className={`mb-0 flex items-center gap-1 ${ htmlFor={`checkbox-${props.pos.key + ind}`}
ind > 0 ? "mt-[3px]" : "mt-[0px]" style={{ fontSize: fontSize, color: fontColor }}
}`} className={`mb-0 flex items-center gap-1`}
> >
<input <input
id={`checkbox-${props.pos.key + ind}`} id={`checkbox-${props.pos.key + ind}`}
style={{ style={{ width: fontSize, height: fontSize }}
width: fontSize, className="op-checkbox rounded-[1px]"
height: fontSize disabled={props.isNeedSign && isReadOnly}
}} type="checkbox"
className="op-checkbox rounded-[1px]" readOnly
disabled={ checked={!!selectCheckbox(ind, selectedCheckbox)}
props.isNeedSign && />
(props.pos.options?.isReadOnly || {!props.pos.options?.isHideLabel && (
props.data?.signerObjId !== props.signerObjId) <span className="leading-none">{data}</span>
} )}
type="checkbox" </label>
readOnly </div>
checked={!!selectCheckbox(ind, selectedCheckbox)} ))}
/>
{!props.pos.options?.isHideLabel && (
<span className="leading-none">{data}</span>
)}
</label>
</div>
);
})}
</div> </div>
); );
case textInputWidget: case textInputWidget:
@@ -192,24 +197,15 @@ function PlaceholderType(props) {
placeholder={hint || t("widgets-name.text")} placeholder={hint || t("widgets-name.text")}
rows={1} rows={1}
value={widgetValue} value={widgetValue}
className={`${ className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId
? "select-none"
: textWidgetCls
}`}
style={{ style={{
fontSize: fontSize, fontSize: fontSize,
color: fontColor, color: fontColor,
background: props.data?.blockColor, background: isReadOnly ? props.data?.blockColor : "white",
pointerEvents: "none" pointerEvents: "none"
}} }}
readOnly readOnly
disabled={ disabled={props.isNeedSign && isReadOnly}
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
}
cols="50" cols="50"
/> />
) : ( ) : (
@@ -217,13 +213,31 @@ function PlaceholderType(props) {
<span>{hint || widgetTypeTranslation}</span> <span>{hint || widgetTypeTranslation}</span>
</div> </div>
); );
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 (
<CellsWidget
count={count}
height={height}
value={cells.join("")}
editable={props.isPlaceholder}
resizable={props.isPlaceholder}
onCellCountChange={handleCellResize}
/>
);
}
case "dropdown": case "dropdown":
return ( return (
<div <div
style={textWidgetStyle} style={textWidgetStyle}
className="select-none-cls flex justify-between items-center" className="select-none-cls flex justify-between items-center"
> >
{widgetData || hint || widgetTypeTranslation} {widgetData || t("choose-one")}
<i className="fa-light fa-circle-chevron-down mr-1 "></i> <i className="fa-light fa-circle-chevron-down mr-1 "></i>
</div> </div>
); );
@@ -259,13 +273,15 @@ function PlaceholderType(props) {
placeholder={hint || widgetTypeTranslation} placeholder={hint || widgetTypeTranslation}
rows={1} rows={1}
value={widgetValue} value={widgetValue}
className={textWidgetCls} className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
style={{ style={{
fontSize: fontSize, fontSize: fontSize,
color: fontColor, color: fontColor,
background: isReadOnly ? props.data?.blockColor : "white",
pointerEvents: "none" pointerEvents: "none"
}} }}
cols="50" cols="50"
disabled={props.isNeedSign && isReadOnly}
/> />
) : ( ) : (
<div className="flex h-full select-none-cls" style={textWidgetStyle}> <div className="flex h-full select-none-cls" style={textWidgetStyle}>
@@ -280,13 +296,15 @@ function PlaceholderType(props) {
placeholder={hint || widgetTypeTranslation} placeholder={hint || widgetTypeTranslation}
rows={1} rows={1}
value={widgetValue} value={widgetValue}
className={textWidgetCls} className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
style={{ style={{
fontSize: fontSize, fontSize: fontSize,
color: fontColor, color: fontColor,
background: isReadOnly ? props.data?.blockColor : "white",
pointerEvents: "none" pointerEvents: "none"
}} }}
cols="50" cols="50"
disabled={props.isNeedSign && isReadOnly}
/> />
) : ( ) : (
<div style={textWidgetStyle} className="select-none-cls"> <div style={textWidgetStyle} className="select-none-cls">
@@ -301,13 +319,15 @@ function PlaceholderType(props) {
placeholder={hint || widgetTypeTranslation} placeholder={hint || widgetTypeTranslation}
rows={1} rows={1}
value={widgetValue} value={widgetValue}
className={textWidgetCls} className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
style={{ style={{
fontSize: fontSize, fontSize: fontSize,
color: fontColor, color: fontColor,
background: isReadOnly ? props.data?.blockColor : "white",
pointerEvents: "none" pointerEvents: "none"
}} }}
cols="50" cols="50"
disabled={props.isNeedSign && isReadOnly}
/> />
) : ( ) : (
<div style={textWidgetStyle} className="select-none-cls"> <div style={textWidgetStyle} className="select-none-cls">
@@ -405,15 +425,15 @@ function PlaceholderType(props) {
placeholder={hint || widgetTypeTranslation} placeholder={hint || widgetTypeTranslation}
rows={1} rows={1}
value={widgetValue} value={widgetValue}
className={textWidgetCls} className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
style={{ style={{
fontSize: fontSize, fontSize: fontSize,
color: fontColor, color: fontColor,
fontFamily: "Arial, sans-serif", background: isReadOnly ? props.data?.blockColor : "white",
pointerEvents: "none" pointerEvents: "none"
}} }}
disabled
cols="1" cols="1"
disabled={props.isNeedSign && isReadOnly}
/> />
) : ( ) : (
<div style={textWidgetStyle} className="select-none-cls"> <div style={textWidgetStyle} className="select-none-cls">
@@ -421,46 +441,39 @@ function PlaceholderType(props) {
</div> </div>
); );
case radioButtonWidget: 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 ( return (
<div> <div className={radioWrapperClass}>
{props.pos.options?.values.map((data, ind) => { {props.pos.options?.values.map((data, ind) => (
return ( <div key={ind} className="select-none-cls pointer-events-none">
<div key={ind} className="select-none-cls pointer-events-none"> <label
<label htmlFor={`radio-${props.pos.key + ind}`}
htmlFor={`radio-${props.pos.key + ind}`} style={{ fontSize: fontSize, color: fontColor }}
style={{ className="text-xs mb-0 flex items-center gap-1"
fontSize: fontSize, >
color: fontColor, <input
marginTop: ind > 0 ? "5px" : "0px" readOnly
}} id={`radio-${props.pos.key + ind}`}
className="text-xs mb-0 flex items-center gap-1 " style={{ width: fontSize, height: fontSize, lineHeight: 2 }}
> className={`op-radio rounded-full border-black appearance-none bg-white inline-block align-middle relative ${
<input handleRadioCheck(data) ? "checked-radio" : ""
readOnly }`}
id={`radio-${props.pos.key + ind}`} type="radio"
style={{ disabled={props.isNeedSign && isReadOnly}
width: fontSize, checked={handleRadioCheck(data)}
height: fontSize, />
lineHeight: 2 {!props.pos.options?.isHideLabel && (
}} <span className="leading-none">{data}</span>
className={`op-radio rounded-full border- border-black appearance-none bg-white inline-block align-middle relative ${ )}
handleRadioCheck(data) ? "checked-radio" : "" </label>
}`} </div>
type="radio" ))}
disabled={
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
}
checked={handleRadioCheck(data)}
/>
{!props.pos.options?.isHideLabel && (
<span className="leading-none">{data}</span>
)}
</label>
</div>
);
})}
</div> </div>
); );
case textWidget: case textWidget:
@@ -474,7 +487,8 @@ function PlaceholderType(props) {
style={{ style={{
fontFamily: "Arial, sans-serif", fontFamily: "Arial, sans-serif",
fontSize: fontSize, fontSize: fontSize,
color: fontColor color: fontColor,
background: "white"
}} }}
cols="50" cols="50"
/> />
+214 -430
View File
@@ -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) => { const checkSignedSigners = (data) => {
let checkSign = []; let checkSign = [];
//condition to handle quick send flow and using normal request sign flow //condition to handle quick send flow and using normal request sign flow
@@ -91,73 +91,71 @@ function RenderPdf(props) {
: []; : [];
return ( return (
checkSign.length === 0 && checkSign.length === 0 &&
data?.placeHolder?.map((placeData, key) => { data?.placeHolder?.map((placeData, key) => (
return ( <React.Fragment key={key}>
<React.Fragment key={key}> {placeData.pageNumber === props.pageNumber &&
{placeData.pageNumber === props.pageNumber && placeData.pos.map(
placeData.pos.map((pos, ind) => { (pos, ind) =>
return ( pos && (
pos && ( <React.Fragment key={ind}>
<React.Fragment key={ind}> <Placeholder
<Placeholder pos={pos}
pos={pos} handleSignYourselfImageResize={handleImageResize}
handleSignYourselfImageResize={handleImageResize} index={props.pageNumber}
index={props.pageNumber} xyPosition={props.signerPos}
xyPosition={props.signerPos} setXyPosition={props.setSignerPos}
setXyPosition={props.setSignerPos} data={data}
data={data} setIsResize={props.setIsResize}
setIsResize={props.setIsResize} isShowBorder={props.isSelfSign}
isShowBorder={props.isSelfSign} isAlllowModify={props.isAlllowModify}
isAlllowModify={props.isAlllowModify} signerObjId={props.signerObjectId}
signerObjId={props.signerObjectId} isShowDropdown={true}
isShowDropdown={true} isNeedSign={props.pdfRequest}
isNeedSign={props.pdfRequest} isSelfSign={true}
isSelfSign={true} isSignYourself={false}
isSignYourself={false} posWidth={posWidth}
posWidth={posWidth} posHeight={posHeight}
posHeight={posHeight} isDragging={props.isDragging}
isDragging={props.isDragging} pdfDetails={props.pdfDetails}
pdfDetails={props.pdfDetails} unSignedWidgetId={props.unSignedWidgetId}
unSignedWidgetId={props.unSignedWidgetId} setCurrWidgetsDetails={props.setCurrWidgetsDetails}
setCurrWidgetsDetails={props.setCurrWidgetsDetails} uniqueId={props.uniqueId}
uniqueId={props.uniqueId} scale={props.scale}
scale={props.scale} containerWH={props.containerWH}
containerWH={props.containerWH} pdfOriginalWH={props.pdfOriginalWH}
pdfOriginalWH={props.pdfOriginalWH} pageNumber={props.pageNumber}
pageNumber={props.pageNumber} ispublicTemplate={props.ispublicTemplate}
ispublicTemplate={props.ispublicTemplate} handleUserDetails={props.handleUserDetails}
handleUserDetails={props.handleUserDetails} isResize={props.isResize}
isResize={props.isResize} setIsAgreeTour={props.setIsAgreeTour}
setIsAgreeTour={props.setIsAgreeTour} isAgree={props.isAgree}
isAgree={props.isAgree} handleTabDrag={props.handleTabDrag}
handleTabDrag={props.handleTabDrag} handleStop={props.handleStop}
handleStop={props.handleStop} setUniqueId={props.setUniqueId}
setUniqueId={props.setUniqueId} setIsSelectId={props.setIsSelectId}
setIsSelectId={props.setIsSelectId} handleDeleteSign={props.handleDeleteSign}
handleDeleteSign={props.handleDeleteSign} setIsPageCopy={props.setIsPageCopy}
setIsPageCopy={props.setIsPageCopy} handleTextSettingModal={props.handleTextSettingModal}
handleTextSettingModal={props.handleTextSettingModal} setIsCheckbox={props.setIsCheckbox}
setIsCheckbox={props.setIsCheckbox} isFreeResize={false}
isFreeResize={false} isOpenSignPad={true}
isOpenSignPad={true} assignedWidgetId={props.assignedWidgetId}
assignedWidgetId={props.assignedWidgetId} isApplyAll={true}
isApplyAll={true} setCellCount={props.setCellCount}
setFontSize={props.setFontSize} setFontSize={props.setFontSize}
fontSize={props.fontSize} fontSize={props.fontSize}
fontColor={props.fontColor} fontColor={props.fontColor}
setFontColor={props.setFontColor} setFontColor={props.setFontColor}
setRequestSignTour={props.setRequestSignTour} setRequestSignTour={props.setRequestSignTour}
calculateFontsize={calculateFontsize} calculateFontsize={calculateFontsize}
currWidgetsDetails={props?.currWidgetsDetails} currWidgetsDetails={props?.currWidgetsDetails}
setTempSignerId={props.setTempSignerId} setTempSignerId={props.setTempSignerId}
/> />
</React.Fragment> </React.Fragment>
) )
); )}
})} </React.Fragment>
</React.Fragment> ))
);
})
); );
}; };
@@ -172,148 +170,138 @@ function RenderPdf(props) {
} }
}; };
const pdfDataBase64 = `data:application/pdf;base64,${props.pdfBase64Url}`; 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 handlePageLoadSuccess = (page) => {
const containerWidth = props.divRef.current.offsetWidth; // Get container width if (isMobile) {
const viewport = page.getViewport({ scale: 1 }); const containerWidth = props.divRef.current.offsetWidth; // Get container width
const scale = containerWidth / viewport.width; // Scale to fit container width const viewport = page.getViewport({ scale: 1 });
const scaleHeight = viewport.height * scale; const scale = containerWidth / viewport.width; // Scale to fit container width
setScaledHeight(scaleHeight); const scaleHeight = viewport.height * scale;
setScaledHeight(scaleHeight);
}
}; };
return ( return (
<> <>
{props.successEmail && ( {props.successEmail && (
<Alert type={"success"}>{t("success-email-alert")}</Alert> <Alert type={"success"}>{t("success-email-alert")}</Alert>
)} )}
{isMobile ? ( <RSC
<RSC style={{
position: "relative",
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
height: isMobile
? isGuestSigner
? window.innerHeight - 49 // 49 is height of header
: scaledHeight
: `${window.innerHeight}px`,
zIndex: 0
}}
noScrollY={isMobile ? props.scale === 1 : false}
noScrollX={props.scale === 1}
>
<div
data-tut={isMobile ? "reactourForth" : undefined}
className={
isMobile
? `${isGuestSigner ? "30px" : ""} border-[0.1px] border-[#ebe8e8] overflow-x-auto`
: ""
}
style={{ style={{
position: "relative", width:
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px", props.containerWH?.width && props.containerWH?.width * props.scale
//49 is height of header
height: isGuestSigner ? window.innerHeight - 49 : scaledHeight,
zIndex: 0
}} }}
noScrollY={props.scale === 1 ? true : false} ref={props.drop}
noScrollX={props.scale === 1 ? true : false} id="container"
> >
<div {props.pdfLoad !== false &&
data-tut="reactourForth" props.containerWH?.width &&
className={`${ props.pdfOriginalWH.length > 0 && (
isGuestSigner ? "30px" : "" <>
} border-[0.1px] border-[#ebe8e8] overflow-x-auto`} {props.pdfRequest || props.isSelfSign
style={{ ? // request sign, guest sign,
width: props.signerPos?.map((data, key) => (
props.containerWH?.width &&
props.containerWH?.width * props.scale
}}
ref={props.drop}
id="container"
>
{props.containerWH?.width &&
props.pdfOriginalWH.length > 0 &&
(props.pdfRequest || props.isSelfSign
? props.signerPos?.map((data, key) => {
return (
<React.Fragment key={key}> <React.Fragment key={key}>
{checkSignedSigners(data)} {checkSignedSigners(data)}
</React.Fragment> </React.Fragment>
); ))
}) : props.placeholder // placeholdersign document, draft document, create template, draft template
: props.placeholder // placeholder mobile ? props.signerPos?.map((data, ind) => (
? props.signerPos?.map((data, ind) => {
return (
<React.Fragment key={ind}> <React.Fragment key={ind}>
{data?.placeHolder && {data?.placeHolder &&
data?.placeHolder.map((placeData, index) => { data?.placeHolder.map((placeData, index) => (
return ( <React.Fragment key={index}>
<React.Fragment key={index}> {placeData.pageNumber === props.pageNumber &&
{placeData.pageNumber === props.pageNumber && placeData.pos.map((pos) => (
placeData.pos.map((pos) => { <React.Fragment key={pos.key}>
return ( <Placeholder
<React.Fragment key={pos.key}> pos={pos}
<Placeholder setIsPageCopy={props.setIsPageCopy}
pos={pos} handleDeleteSign={
setIsPageCopy={props.setIsPageCopy} props.handleDeleteSign
handleDeleteSign={ }
props.handleDeleteSign handleTabDrag={props.handleTabDrag}
} handleStop={props.handleStop}
handleTabDrag={props.handleTabDrag} handleSignYourselfImageResize={
handleStop={props.handleStop} handleImageResize
handleSignYourselfImageResize={ }
handleImageResize index={props.pageNumber}
} xyPosition={props.signerPos}
index={props.pageNumber} setXyPosition={props.setSignerPos}
xyPosition={props.signerPos} data={data}
setXyPosition={props.setSignerPos} setIsResize={props.setIsResize}
data={data} setShowDropdown={props.setShowDropdown}
setIsResize={props.setIsResize} isShowBorder={true}
setShowDropdown={ isPlaceholder={true}
props.setShowDropdown setUniqueId={props.setUniqueId}
} handleLinkUser={props.handleLinkUser}
isShowBorder={true} isSignYourself={false}
isPlaceholder={true} posWidth={posWidth}
setUniqueId={props.setUniqueId} posHeight={posHeight}
handleLinkUser={ isDragging={props.isDragging}
props.handleLinkUser setIsValidate={props.setIsValidate}
} setIsRadio={props.setIsRadio}
isSignYourself={false} setIsCheckbox={props.setIsCheckbox}
posWidth={posWidth} setCurrWidgetsDetails={
posHeight={posHeight} props.setCurrWidgetsDetails
isDragging={props.isDragging} }
setIsValidate={props.setIsValidate} handleNameModal={props.handleNameModal}
setIsRadio={props.setIsRadio} setTempSignerId={props.setTempSignerId}
setIsCheckbox={props.setIsCheckbox} uniqueId={props.uniqueId}
setCurrWidgetsDetails={ handleTextSettingModal={
props.setCurrWidgetsDetails props.handleTextSettingModal
} }
handleNameModal={ scale={props.scale}
props.handleNameModal containerWH={props.containerWH}
} pdfOriginalWH={props.pdfOriginalWH}
setTempSignerId={ pageNumber={props.pageNumber}
props.setTempSignerId setIsSelectId={props.setIsSelectId}
} fontSize={props.fontSize}
uniqueId={props.uniqueId} setFontSize={props.setFontSize}
handleTextSettingModal={ setCellCount={props.setCellCount}
props.handleTextSettingModal fontColor={props.fontColor}
} setFontColor={props.setFontColor}
scale={props.scale} isResize={props.isResize}
containerWH={props.containerWH} unSignedWidgetId={
pdfOriginalWH={props.pdfOriginalWH} props.unSignedWidgetId
pageNumber={props.pageNumber} }
setIsSelectId={props.setIsSelectId} isFreeResize={true}
fontSize={props.fontSize} calculateFontsize={calculateFontsize}
setFontSize={props.setFontSize} currWidgetsDetails={
fontColor={props.fontColor} props?.currWidgetsDetails
setFontColor={props.setFontColor} }
isResize={props.isResize} />
unSignedWidgetId={ </React.Fragment>
props.unSignedWidgetId ))}
} </React.Fragment>
isFreeResize={true} ))}
calculateFontsize={
calculateFontsize
}
currWidgetsDetails={
props?.currWidgetsDetails
}
/>
</React.Fragment>
);
})}
</React.Fragment>
);
})}
</React.Fragment> </React.Fragment>
); ))
}) : !props.pdfDetails?.[0]?.IsCompleted && // signyourself flow
: !props.pdfDetails?.[0]?.IsCompleted && props.xyPosition?.map((data, ind) => (
props.xyPosition?.map((data, ind) => {
return (
<React.Fragment key={ind}> <React.Fragment key={ind}>
{data.pageNumber === props.pageNumber && {data.pageNumber === props.pageNumber &&
data.pos.map((pos, id) => { data.pos.map(
return ( (pos, id) =>
pos && ( pos && (
<Placeholder <Placeholder
key={id} key={id}
@@ -359,247 +347,43 @@ function RenderPdf(props) {
} }
/> />
) )
); )}
})}
</React.Fragment> </React.Fragment>
); ))}
}))} </>
{/* Mobile */} )}
<Document <Document
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>} error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
onLoadError={() => props.setPdfLoad(false)} onLoadError={(e) => {
loading={t("loading-doc")} console.log("PDF load error", e);
onLoadSuccess={props.pageDetails} props.setPdfLoad(false);
onClick={() =>
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
}
file={pdfDataBase64}
>
<Page
onLoadSuccess={handlePageLoadSuccess}
scale={props.scale || 1}
key={props.index}
pageNumber={props.pageNumber}
width={props.containerWH.width}
renderAnnotationLayer={false}
renderTextLayer={false}
onGetAnnotationsError={(error) => {
console.log("annotation error", error);
}}
className="select-none touch-callout-none"
/>
</Document>
</div>
</RSC>
) : (
<RSC
style={{
position: "relative",
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
height: window.innerHeight + "px",
zIndex: 0
}}
noScrollY={false}
noScrollX={props.scale === 1 ? true : false}
>
<div
style={{
width:
props.containerWH?.width &&
props.containerWH?.width * props.scale
}} }}
ref={props.drop} loading={t("loading-doc")}
id="container" onLoadSuccess={(pdf) => {
props.setPdfLoad(true);
props.pageDetails(pdf);
}}
onClick={() =>
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
}
file={pdfDataBase64}
> >
{props.pdfLoad && <Page
props.containerWH?.width && key={props.index}
props.pdfOriginalWH.length > 0 && onLoadSuccess={handlePageLoadSuccess}
(props.pdfRequest || props.isSelfSign //pdf request sign flow width={props.containerWH.width}
? props.signerPos?.map((data, key) => { scale={props.scale || 1}
return ( className={isMobile ? "select-none touch-callout-none" : "-z-[1]"}
<React.Fragment key={key}> pageNumber={props.pageNumber}
{checkSignedSigners(data)} renderAnnotationLayer={false}
</React.Fragment> renderTextLayer={false}
); onGetAnnotationsError={(error) => {
}) console.log("annotation error", error);
: props.placeholder //placeholder and template flow }}
? props.signerPos.map((data, ind) => { />
return ( </Document>
<React.Fragment key={ind}> </div>
{data?.placeHolder && </RSC>
data?.placeHolder.map((placeData, index) => {
return (
<React.Fragment key={index}>
{placeData.pageNumber === props.pageNumber &&
placeData.pos.map((pos) => {
return (
<React.Fragment key={pos.key}>
<Placeholder
pos={pos}
setIsPageCopy={props.setIsPageCopy}
handleDeleteSign={
props.handleDeleteSign
}
handleTabDrag={props.handleTabDrag}
handleStop={props.handleStop}
handleSignYourselfImageResize={
handleImageResize
}
index={props.pageNumber}
xyPosition={props.signerPos}
setXyPosition={props.setSignerPos}
data={data}
setIsResize={props.setIsResize}
setShowDropdown={
props.setShowDropdown
}
isShowBorder={true}
isPlaceholder={true}
setUniqueId={props.setUniqueId}
handleLinkUser={
props.handleLinkUser
}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
isDragging={props.isDragging}
setIsValidate={props.setIsValidate}
setIsRadio={props.setIsRadio}
setIsCheckbox={props.setIsCheckbox}
setCurrWidgetsDetails={
props.setCurrWidgetsDetails
}
handleNameModal={
props.handleNameModal
}
setTempSignerId={
props.setTempSignerId
}
uniqueId={props.uniqueId}
handleTextSettingModal={
props.handleTextSettingModal
}
scale={props.scale}
containerWH={props.containerWH}
pdfOriginalWH={props.pdfOriginalWH}
pageNumber={props.pageNumber}
setIsSelectId={props.setIsSelectId}
fontSize={props.fontSize}
setFontSize={props.setFontSize}
fontColor={props.fontColor}
setFontColor={props.setFontColor}
isResize={props.isResize}
unSignedWidgetId={
props.unSignedWidgetId
}
isFreeResize={true}
calculateFontsize={
calculateFontsize
}
currWidgetsDetails={
props?.currWidgetsDetails
}
/>
</React.Fragment>
);
})}
</React.Fragment>
);
})}
</React.Fragment>
);
})
: !props.pdfDetails?.[0]?.IsCompleted &&
props.xyPosition?.map((data, ind) => {
// signyourself flow
return (
<React.Fragment key={ind}>
{data.pageNumber === props.pageNumber &&
data.pos.map((pos) => {
return (
<React.Fragment key={pos.key}>
<Placeholder
pos={pos}
setIsPageCopy={props.setIsPageCopy}
handleDeleteSign={props.handleDeleteSign}
handleTabDrag={props.handleTabDrag}
handleStop={(event, dragElement) =>
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
}
/>
</React.Fragment>
);
})}
</React.Fragment>
);
}))}
{/* large device */}
{/* this component for render pdf document is in middle of the component */}
<Document
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
onLoadError={() => props.setPdfLoad(false)}
loading={t("loading-doc")}
onLoadSuccess={props.pageDetails}
onClick={() =>
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
}
file={pdfDataBase64}
>
<Page
key={props.index}
width={props.containerWH.width}
scale={props.scale || 1}
className={"-z-[1]"} // when user zoom-in in tablet widgets move backward that's why pass -z-[1]
pageNumber={props.pageNumber}
renderAnnotationLayer={false}
renderTextLayer={false}
onGetAnnotationsError={(error) => {
console.log("annotation error", error);
}}
/>
</Document>
</div>
</RSC>
)}
</> </>
); );
} }
@@ -8,6 +8,7 @@ import {
isMobile, isMobile,
radioButtonWidget, radioButtonWidget,
textInputWidget, textInputWidget,
cellsWidget,
textWidget, textWidget,
widgets widgets
} from "../../constant/Utils"; } from "../../constant/Utils";
@@ -37,6 +38,10 @@ function WidgetComponent(props) {
type: "BOX", type: "BOX",
item: { type: "BOX", id: 7, text: textInputWidget } item: { type: "BOX", id: 7, text: textInputWidget }
}); });
const [, cells] = useDrag({
type: "BOX",
item: { type: "BOX", id: 17, text: cellsWidget }
});
const [, initials] = useDrag({ const [, initials] = useDrag({
type: "BOX", type: "BOX",
item: { type: "BOX", id: 8, text: "initials" } item: { type: "BOX", id: 8, text: "initials" }
@@ -89,6 +94,7 @@ function WidgetComponent(props) {
date, date,
text, text,
textInput, textInput,
cells,
checkbox, checkbox,
dropdown, dropdown,
radioButton, radioButton,
@@ -132,7 +138,9 @@ function WidgetComponent(props) {
); );
const filterWidgets = widget.filter( const filterWidgets = widget.filter(
(data) => (data) =>
!["dropdown", radioButtonWidget, textInputWidget].includes(data.type) !["dropdown", radioButtonWidget, textInputWidget].includes(
data.type
)
); );
const textWidgetData = widget.filter((data) => data.type !== textWidget); const textWidgetData = widget.filter((data) => data.type !== textWidget);
const updateWidgets = props.isSignYourself const updateWidgets = props.isSignYourself
@@ -5,6 +5,7 @@ import RegexParser from "regex-parser";
import { import {
signatureTypes, signatureTypes,
textInputWidget, textInputWidget,
cellsWidget,
textWidget textWidget
} from "../../constant/Utils"; } from "../../constant/Utils";
import { fontColorArr, fontsizeArr } from "../../constant/Utils"; import { fontColorArr, fontsizeArr } from "../../constant/Utils";
@@ -19,7 +20,8 @@ const WidgetNameModal = (props) => {
status: "required", status: "required",
hint: "", hint: "",
textvalidate: "", textvalidate: "",
isReadOnly: false isReadOnly: false,
cellCount: 5
}); });
const [isValid, setIsValid] = useState(true); const [isValid, setIsValid] = useState(true);
const statusArr = ["Required", "Optional"]; const statusArr = ["Required", "Optional"];
@@ -51,12 +53,14 @@ const WidgetNameModal = (props) => {
props.defaultdata?.options?.validation?.type === "regex" props.defaultdata?.options?.validation?.type === "regex"
? props.defaultdata?.options?.validation?.pattern ? props.defaultdata?.options?.validation?.pattern
: props.defaultdata?.options?.validation?.type || "", : props.defaultdata?.options?.validation?.type || "",
isReadOnly: props.defaultdata?.options?.isReadOnly || false isReadOnly: props.defaultdata?.options?.isReadOnly || false,
cellCount: props.defaultdata?.options?.cellCount || 5
}); });
} else { } else {
setFormdata({ setFormdata({
...formdata, ...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); props.handleData(data, props.defaultdata?.type);
} }
} else { } else {
const isTextInput = props.defaultdata?.type === textInputWidget; const isTextInput = [textInputWidget, cellsWidget].includes(
props.defaultdata?.type
);
const { isReadOnly, defaultValue, status } = formdata; const { isReadOnly, defaultValue, status } = formdata;
// If its a textinput widget, enforce that read-only fields have // If its a textinput widget, enforce that read-only fields have
// either a defaultValue or an "optional" status. // either a defaultValue or an "optional" status.
@@ -104,7 +110,8 @@ const WidgetNameModal = (props) => {
defaultValue: "", defaultValue: "",
status: "required", status: "required",
hint: "", hint: "",
textvalidate: "" textvalidate: "",
cellCount: 5
}); });
setSignatureType(signTypes); setSignatureType(signTypes);
} }
@@ -125,7 +132,11 @@ const WidgetNameModal = (props) => {
} else { } else {
setIsValid(true); 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) { function handleValidation(type) {
@@ -136,6 +147,8 @@ const WidgetNameModal = (props) => {
return "/^\\d+$/"; return "/^\\d+$/";
case "text": case "text":
return "/^[a-zA-Zs]+$/"; return "/^[a-zA-Zs]+$/";
case "ssn":
return "^\\d{3}-\\d{2}-\\d{4}$";
default: default:
return type; return type;
} }
@@ -163,7 +176,7 @@ const WidgetNameModal = (props) => {
<form <form
onSubmit={handleSubmit} onSubmit={handleSubmit}
className={`${ className={`${
props.defaultdata?.type === textInputWidget [textInputWidget, cellsWidget].includes(props.defaultdata?.type)
? "pt-0" ? "pt-0"
: ["signature", "initials"].includes(props.defaultdata?.type) : ["signature", "initials"].includes(props.defaultdata?.type)
? "pt-2" ? "pt-2"
@@ -187,7 +200,21 @@ const WidgetNameModal = (props) => {
/> />
</div> </div>
)} )}
{props.defaultdata?.type === textInputWidget && ( {props.defaultdata?.type === cellsWidget && (
<div className="mb-[0.75rem] text-[13px]">
<label htmlFor="cellCount">{t("cell-count")}</label>
<input
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
type="number"
min="1"
name="cellCount"
value={formdata.cellCount}
onChange={(e) => handleChange(e)}
required
/>
</div>
)}
{[textInputWidget, cellsWidget].includes(props.defaultdata?.type) && (
<> <>
<div className="mb-[0.75rem]"> <div className="mb-[0.75rem]">
<label htmlFor="name" className="text-[13px]"> <label htmlFor="name" className="text-[13px]">
@@ -199,6 +226,11 @@ const WidgetNameModal = (props) => {
value={formdata.defaultValue} value={formdata.defaultValue}
onChange={(e) => handledefaultChange(e)} onChange={(e) => handledefaultChange(e)}
autoComplete="off" autoComplete="off"
maxLength={
props.defaultdata?.type === cellsWidget
? formdata.cellCount
: undefined
}
onBlur={() => { onBlur={() => {
if (isValid === false) { if (isValid === false) {
setFormdata({ ...formdata, defaultValue: "" }); setFormdata({ ...formdata, defaultValue: "" });
@@ -250,7 +282,9 @@ const WidgetNameModal = (props) => {
); );
})} })}
</div> </div>
{[textInputWidget].includes(props.defaultdata?.type) && ( {[textInputWidget, cellsWidget].includes(
props.defaultdata?.type
) && (
<div className="flex items-center"> <div className="flex items-center">
<input <input
id="isReadOnly" id="isReadOnly"
@@ -265,7 +299,7 @@ const WidgetNameModal = (props) => {
})) }))
} }
/> />
<label className="ml-1 mb-0" htmlFor="isreadonly"> <label className="ml-1.5 mb-0 capitalize text-[13px]" htmlFor="isreadonly">
{t("read-only")} {t("read-only")}
</label> </label>
</div> </div>
@@ -317,6 +351,7 @@ const WidgetNameModal = (props) => {
{[ {[
textInputWidget, textInputWidget,
textWidget, textWidget,
cellsWidget,
"name", "name",
"company", "company",
"job title", "job title",
@@ -15,9 +15,11 @@ import {
radioButtonWidget, radioButtonWidget,
selectCheckbox, selectCheckbox,
textInputWidget, textInputWidget,
cellsWidget,
textWidget, textWidget,
years years
} from "../../constant/Utils"; } from "../../constant/Utils";
import CellsWidget from "./CellsWidget";
import DatePicker from "react-datepicker"; import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css"; import "react-datepicker/dist/react-datepicker.css";
import SignatureCanvas from "react-signature-canvas"; import SignatureCanvas from "react-signature-canvas";
@@ -93,7 +95,6 @@ function WidgetsValueModal(props) {
const [isTab, setIsTab] = useState(""); const [isTab, setIsTab] = useState("");
const [textWidth, setTextWidth] = useState(0); const [textWidth, setTextWidth] = useState(0);
const [textHeight, setTextHeight] = useState(0); const [textHeight, setTextHeight] = useState(0);
const [signatureType, setSignatureType] = useState("");
const [isSignTypes, setIsSignTypes] = useState(true); const [isSignTypes, setIsSignTypes] = useState(true);
const [typedSignature, setTypedSignature] = useState(""); const [typedSignature, setTypedSignature] = useState("");
const [selectDate, setSelectDate] = useState({}); const [selectDate, setSelectDate] = useState({});
@@ -119,13 +120,33 @@ function WidgetsValueModal(props) {
const currentUserName = jsonSender && jsonSender?.name; const currentUserName = jsonSender && jsonSender?.name;
const widgetTypeTranslation = t(`widgets-name.${currWidgetsDetails?.type}`); const widgetTypeTranslation = t(`widgets-name.${currWidgetsDetails?.type}`);
const [widgetValue, setWidgetValue] = useState( const [widgetValue, setWidgetValue] = useState(
currWidgetsDetails?.options?.response || currWidgetsDetails.type !== "checkbox" &&
currWidgetsDetails?.options?.defaultValue (currWidgetsDetails?.options?.response ||
currWidgetsDetails?.options?.defaultValue)
); );
const [selectedCheckbox, setSelectedCheckbox] = useState( const [cellsValue, setCellsValue] = useState(() => {
currWidgetsDetails?.options?.response || const count = currWidgetsDetails?.options?.cellCount || 5;
const val =
currWidgetsDetails?.options?.response ||
currWidgetsDetails?.options?.defaultValue || 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( const [startDate, setStartDate] = useState(
currWidgetsDetails?.options?.response currWidgetsDetails?.options?.response
@@ -156,34 +177,6 @@ function WidgetsValueModal(props) {
setHint(currWidgetsDetails?.type); 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 // eslint-disable-next-line react-hooks/exhaustive-deps
}, [currWidgetsDetails]); // Added currWidgetsDetails to dependency array for reset logic }, [currWidgetsDetails]); // Added currWidgetsDetails to dependency array for reset logic
@@ -403,7 +396,6 @@ function WidgetsValueModal(props) {
const tab = signatureTypes?.[getIndex].name; const tab = signatureTypes?.[getIndex].name;
if (tab === "draw") { if (tab === "draw") {
setIsTab("draw"); setIsTab("draw");
setSignatureType("draw");
} else if (tab === "upload") { } else if (tab === "upload") {
setIsImageSelect(true); setIsImageSelect(true);
setIsTab("uploadImage"); setIsTab("uploadImage");
@@ -449,7 +441,32 @@ function WidgetsValueModal(props) {
setTypedSignature(""); setTypedSignature("");
} }
} else { } 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 //function for set signature url
@@ -583,7 +600,7 @@ function WidgetsValueModal(props) {
if (isTab === "mysignature") { if (isTab === "mysignature") {
setSignature(""); setSignature("");
if (currWidgetsDetails?.type === "initials") { if (currWidgetsDetails?.type === "initials") {
handleSaveSignature(signatureType, "initials"); handleSaveSignature(isTab, "initials");
} else { } else {
handleSaveSignature(null, "default"); handleSaveSignature(null, "default");
} }
@@ -604,13 +621,13 @@ function WidgetsValueModal(props) {
} else { } else {
setSignature(""); setSignature("");
canvasRef?.current?.clear(); canvasRef?.current?.clear();
handleSaveSignature(signatureType); handleSaveSignature(isTab);
} }
} }
setPenColor("blue"); setPenColor("blue");
} else { } else {
setSignature(""); setSignature("");
handleSaveImage(signatureType); handleSaveImage();
} }
setIsImageSelect(false); setIsImageSelect(false);
setIsDefaultSign(false); setIsDefaultSign(false);
@@ -649,24 +666,21 @@ function WidgetsValueModal(props) {
}, [fontSelect]); }, [fontSelect]);
useEffect(() => { useEffect(() => {
if ( if (currWidgetsDetails?.options?.response) {
["signature", "initials"].includes(currWidgetsDetails?.type) && const url = currWidgetsDetails?.options?.response;
widgetValue if (["signature", "initials"].includes(currWidgetsDetails?.type)) {
) { if (isTab === "draw" && currWidgetsDetails?.signatureType === "draw") {
if (isTab === "draw" && currWidgetsDetails?.signatureType === "draw") { setSignature(url);
setSignature(widgetValue); // Load the default signature after the component mounts
} else if (isTab === "uploadImage" && currWidgetsDetails?.ImageType) { if (canvasRef.current) {
setImage({ imgType: currWidgetsDetails?.ImageType, src: widgetValue }); 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") { if (isTab === "type") {
const trimmedName = typedSignature const trimmedName = typedSignature
@@ -953,6 +967,65 @@ function WidgetsValueModal(props) {
uniqueId 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 //function is used to show widgets on modal according to selected widget type checkbox/date/radio/drodown/textbox/signature/image
const getWidgetType = (type) => { const getWidgetType = (type) => {
switch (type) { switch (type) {
@@ -983,7 +1056,6 @@ function WidgetsValueModal(props) {
setIsDefaultSign(true); setIsDefaultSign(true);
setIsImageSelect(true); setIsImageSelect(true);
setIsTab("mysignature"); setIsTab("mysignature");
setSignatureType("");
setImage(); setImage();
}} }}
className={`${ className={`${
@@ -1003,7 +1075,6 @@ function WidgetsValueModal(props) {
setIsDefaultSign(true); setIsDefaultSign(true);
setIsImageSelect(true); setIsImageSelect(true);
setIsTab("mysignature"); setIsTab("mysignature");
setSignatureType("");
setImage(); setImage();
}} }}
className={`${ className={`${
@@ -1040,7 +1111,6 @@ function WidgetsValueModal(props) {
setIsDefaultSign(false); setIsDefaultSign(false);
setIsImageSelect(true); setIsImageSelect(true);
setIsTab("uploadImage"); setIsTab("uploadImage");
setSignatureType("");
}} }}
className={`${ className={`${
isTab === "uploadImage" && `${isTabCls}` isTab === "uploadImage" && `${isTabCls}`
@@ -1057,7 +1127,6 @@ function WidgetsValueModal(props) {
setIsDefaultSign(false); setIsDefaultSign(false);
setIsImageSelect(false); setIsImageSelect(false);
setIsTab("type"); setIsTab("type");
setSignatureType("");
setImage(); setImage();
}} }}
className={`${ className={`${
@@ -1326,69 +1395,66 @@ function WidgetsValueModal(props) {
</div> </div>
</> </>
) : ( ) : (
<div> <div className="mx-3 mb-6 mt-3">
<div className="relative flex flex-row items-center justify-between"> <p>{t("at-least-one-signature-type")}</p>
<div className="text-base-content font-bold text-lg">
{t("signature")}
</div>
<div
className="text-[1.5rem] cursor-pointer"
onClick={handleCancelBtn}
>
&times;
</div>
</div>
<div className="mx-3 mb-6 mt-3">
<p>{t("at-least-one-signature-type")}</p>
</div>
</div> </div>
)} )}
</div> </div>
); );
case "checkbox": 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 ( return (
<div className="border-[1px] border-gray-300 rounded-[2px] pt-1 px-2.5"> <div
{currWidgetsDetails?.options?.values?.map((data, ind) => { className={`border-[1px] border-gray-300 rounded-[2px] pt-1 px-2.5 ${checkBoxWrapperClass}`}
return ( >
<div key={ind} className=" select-none-cls"> {currWidgetsDetails?.options?.values?.map((data, ind) => (
<label <div key={ind} className=" select-none-cls">
htmlFor={`checkbox-${currWidgetsDetails?.key + ind}`} <label
className="text-xs flex items-center gap-1" htmlFor={`checkbox-${currWidgetsDetails?.key + ind}`}
> className="text-xs flex items-center gap-1"
<input >
id={`checkbox-${currWidgetsDetails?.key + ind}`} <input
className={`${ id={`checkbox-${currWidgetsDetails?.key + ind}`}
ind === 0 ? "mt-0" : "mt-[5px]" className={`${
} op-checkbox op-checkbox-xs rounded-[1px] mt-1`} ind === 0 ? "mt-0" : "mt-[5px]"
type="checkbox" } op-checkbox op-checkbox-xs rounded-[1px] mt-1`}
checked={!!selectCheckbox(ind, selectedCheckbox)} type="checkbox"
onChange={(e) => { checked={!!selectCheckbox(ind, selectedCheckbox)}
if (e.target.checked) { onChange={(e) => {
const maxRequired = if (e.target.checked) {
currWidgetsDetails?.options?.validation const maxRequired =
?.maxRequiredCount; currWidgetsDetails?.options?.validation
const maxCountInt = ?.maxRequiredCount;
maxRequired && parseInt(maxRequired); const maxCountInt =
if (maxCountInt > 0) { maxRequired && parseInt(maxRequired);
if ( if (maxCountInt > 0) {
selectedCheckbox && if (
selectedCheckbox?.length <= maxCountInt - 1 selectedCheckbox &&
) { selectedCheckbox?.length <= maxCountInt - 1
handleCheckboxValue(e.target.checked, ind); ) {
}
} else {
handleCheckboxValue(e.target.checked, ind); handleCheckboxValue(e.target.checked, ind);
} }
} else { } else {
handleCheckboxValue(e.target.checked, ind); handleCheckboxValue(e.target.checked, ind);
} }
}} } else {
/> handleCheckboxValue(e.target.checked, ind);
{data} }
</label> }}
</div> />
); {data}
})} </label>
</div>
))}
</div> </div>
); );
case textInputWidget: case textInputWidget:
@@ -1401,6 +1467,19 @@ function WidgetsValueModal(props) {
className={textInputcls} className={textInputcls}
/> />
); );
case cellsWidget:
return (
<CellsWidget
count={cellsValue.length}
height="100%"
value={cellsValue.join("")}
editable={true}
resizable={false}
onChange={handleCellsInput}
onKeyDown={handleCellsKeyDown}
inputRefs={cellRefs}
/>
);
case "dropdown": case "dropdown":
return ( return (
<select <select
@@ -1418,18 +1497,11 @@ function WidgetsValueModal(props) {
> >
{currWidgetsDetails?.options?.name} {currWidgetsDetails?.options?.name}
</option> </option>
{currWidgetsDetails?.options?.values?.map((data, ind) => (
{currWidgetsDetails?.options?.values?.map((data, ind) => { <option key={ind} value={data}>
return ( {data}
<option </option>
// style={{ fontSize: fontSize, color: fontColor }} ))}
key={ind}
value={data}
>
{data}
</option>
);
})}
</select> </select>
); );
case "name": case "name":
@@ -1529,30 +1601,38 @@ function WidgetsValueModal(props) {
</> </>
); );
case radioButtonWidget: 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 ( return (
<div className="border-[1px] border-gray-300 rounded-[2px] pt-1 px-2.5"> <div
{currWidgetsDetails?.options?.values.map((data, ind) => { className={`border-[1px] border-gray-300 rounded-[2px] pt-1 px-2.5 ${radioWrapperClass}`}
return ( >
<div key={ind} className="select-none-cls"> {currWidgetsDetails?.options?.values.map((data, ind) => (
<label <div key={ind} className="select-none-cls">
htmlFor={`radio-${currWidgetsDetails?.key + ind}`} <label
className="cursor-pointer flex items-center text-sm gap-1" htmlFor={`radio-${currWidgetsDetails?.key + ind}`}
> className="cursor-pointer flex items-center text-sm gap-1"
<input >
id={`radio-${currWidgetsDetails?.key + ind}`} <input
className={`op-radio op-radio-xs mt-1`} id={`radio-${currWidgetsDetails?.key + ind}`}
type="radio" className={`op-radio op-radio-xs mt-1`}
value={data} type="radio"
checked={handleRadioCheck(data)} value={data}
onChange={(e) => { checked={handleRadioCheck(data)}
handleCheckRadio(e.target.value); onChange={(e) => {
}} handleCheckRadio(e.target.value);
/> }}
<span>{data}</span> />
</label> <span>{data}</span>
</div> </label>
); </div>
})} ))}
</div> </div>
); );
case textWidget: case textWidget:
@@ -1590,15 +1670,9 @@ function WidgetsValueModal(props) {
const minCount = const minCount =
currWidgetsDetails?.options?.validation?.minRequiredCount; currWidgetsDetails?.options?.validation?.minRequiredCount;
const parseMin = minCount && parseInt(minCount); const parseMin = minCount && parseInt(minCount);
//get maximum required count if exist if (parseMin > 0) {
const maxCount =
currWidgetsDetails?.options?.validation?.maxRequiredCount;
const parseMax = maxCount && parseInt(maxCount);
if (parseMin > 0 && parseMax > 0) {
isRequired = true; isRequired = true;
} }
} else if (isRadio) {
isRequired = true;
} else { } else {
isRequired = currWidgetsDetails.options?.status === "required"; isRequired = currWidgetsDetails.options?.status === "required";
} }
@@ -1799,11 +1873,60 @@ function WidgetsValueModal(props) {
setUniqueId(tempSignerId); setUniqueId(tempSignerId);
} }
}; };
//function is used to excecute on click finish button functionality
const handleFinish = () => { const handleFinish = () => {
props?.finishDocument(); props?.finishDocument();
dispatch(setIsShowModal({})); 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 ( return (
<> <>
<ModalUi <ModalUi
+223 -165
View File
@@ -20,6 +20,7 @@ export const isTabAndMobile = window.innerWidth < 1023;
export const textInputWidget = "text input"; export const textInputWidget = "text input";
export const textWidget = "text"; export const textWidget = "text";
export const radioButtonWidget = "radio button"; export const radioButtonWidget = "radio button";
export const cellsWidget = "cells";
export function getEnv() { export function getEnv() {
return window?.RUNTIME_ENV || {}; return window?.RUNTIME_ENV || {};
} }
@@ -257,6 +258,7 @@ export const widgets = [
{ type: "date", icon: "fa-light fa-calendar-days", iconSize: "20px" }, { type: "date", icon: "fa-light fa-calendar-days", iconSize: "20px" },
{ type: textWidget, icon: "fa-light fa-text-width", iconSize: "20px" }, { type: textWidget, icon: "fa-light fa-text-width", iconSize: "20px" },
{ type: textInputWidget, icon: "fa-light fa-font", iconSize: "21px" }, { type: textInputWidget, icon: "fa-light fa-font", iconSize: "21px" },
{ type: cellsWidget, icon: "fa-light fa-table-cells", iconSize: "20px" },
{ type: "checkbox", icon: "fa-light fa-square-check", iconSize: "22px" }, { type: "checkbox", icon: "fa-light fa-square-check", iconSize: "22px" },
{ {
type: "dropdown", type: "dropdown",
@@ -349,6 +351,15 @@ export const addWidgetOptions = (type, signer, widgetValue) => {
}; };
case textInputWidget: case textInputWidget:
return { ...status, name: "Text", isReadOnly: false }; return { ...status, name: "Text", isReadOnly: false };
case cellsWidget:
return {
...status,
name: "Cells",
cellCount: 5,
defaultValue: "",
validation: { type: "", pattern: "" },
isReadOnly: false
};
case "initials": case "initials":
return { ...status, name: "Initials" }; return { ...status, name: "Initials" };
case "name": case "name":
@@ -416,6 +427,14 @@ export const addWidgetSelfsignOptions = (type, getWidgetValue, owner) => {
return { name: "Checkbox" }; return { name: "Checkbox" };
case textWidget: case textWidget:
return { name: "Text" }; return { name: "Text" };
case cellsWidget:
return {
name: "Cells",
cellCount: 5,
defaultValue: "",
validation: { type: "", pattern: "" },
isReadOnly: false
};
case "initials": case "initials":
return { name: "Initials" }; return { name: "Initials" };
case "name": case "name":
@@ -486,6 +505,8 @@ export const defaultWidthHeight = (type) => {
return { width: 15, height: 19 }; return { width: 15, height: 19 };
case textInputWidget: case textInputWidget:
return { width: 150, height: 19 }; return { width: 150, height: 19 };
case cellsWidget:
return { width: 112, height: 22 };
case "dropdown": case "dropdown":
return { width: 120, height: 22 }; return { width: 120, height: 22 };
case "initials": case "initials":
@@ -685,7 +706,7 @@ export const signPdfFun = async (
export const randomId = () => { export const randomId = () => {
// 1. Grab a cryptographically-secure 32-bit random value // 1. Grab a cryptographically-secure 32-bit random value
const randomBytes = crypto.getRandomValues(new Uint32Array(1)); 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 // 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); const eightDigit = 10_000_000 + (raw % 90_000_000);
@@ -863,7 +884,8 @@ export const onChangeInput = (
initial, initial,
dateFormat, dateFormat,
fontSize, fontSize,
fontColor fontColor,
isDateReadOnly
) => { ) => {
const isSigners = xyPosition.some((data) => data.signerPtr); const isSigners = xyPosition.some((data) => data.signerPtr);
let filterSignerPos; let filterSignerPos;
@@ -892,6 +914,7 @@ export const onChangeInput = (
response: value, response: value,
fontSize: fontSize, fontSize: fontSize,
fontColor: fontColor, fontColor: fontColor,
isReadOnly: isDateReadOnly || false,
validation: { validation: {
type: "date-format", type: "date-format",
format: dateFormat // This indicates the required date format explicitly. format: dateFormat // This indicates the required date format explicitly.
@@ -969,7 +992,6 @@ export const onChangeInput = (
setXyPosition(updatePlaceholder); setXyPosition(updatePlaceholder);
} }
}; };
//function to increase height of text area on press enter //function to increase height of text area on press enter
export const onChangeHeightOfTextArea = ( export const onChangeHeightOfTextArea = (
height, height,
@@ -1213,8 +1235,8 @@ export function onSaveSign(
} }
return obj; return obj;
}); });
//condition when user click on apply(signature,image,typed signature or defaullt signature) all widgets on signature pad for same widgets //condition when draw/upload signature/initials then apply it all related to widgets (signature,image,typed signature or default signature)
if (isAutoSign) { if (isApplyAll || isAutoSign) {
const updatedArray = updateXYposition.map((page) => ({ const updatedArray = updateXYposition.map((page) => ({
...page, ...page,
pos: page.pos.map( pos: page.pos.map(
@@ -1233,26 +1255,6 @@ export function onSaveSign(
) )
})); }));
return updatedArray; 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 { } else {
return updateXYposition; return updateXYposition;
} }
@@ -1361,35 +1363,13 @@ export function onSaveImage(
} }
return obj; return obj;
}); });
//condition when user click on apply(stamp) all widgets on signature pad for same widgets //condition when user upload(stamp) then apply it all related to widgets
if (isAutoSign) { if (isApplyAll || isAutoSign) {
const updatedArray = updateXYposition.map((page) => ({ const updatedArray = updateXYposition.map((page) => ({
...page, ...page,
pos: page.pos.map( pos: page.pos.map(
(item) => (item) =>
item.type === widgetsType item.type === widgetsType && item.type !== "image"
? {
...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, ...item,
Width: getIMGWH.newWidth, Width: getIMGWH.newWidth,
@@ -1655,6 +1635,7 @@ export const multiSignEmbed = async (
const WidgetsTypeTextExist = [ const WidgetsTypeTextExist = [
textWidget, textWidget,
textInputWidget, textInputWidget,
cellsWidget,
"name", "name",
"company", "company",
"job title", "job title",
@@ -1668,9 +1649,10 @@ export const multiSignEmbed = async (
const color = position?.options?.fontColor; const color = position?.options?.fontColor;
const updateColorInRgb = getWidgetsFontColor(color); const updateColorInRgb = getWidgetsFontColor(color);
const fontSize = parseInt(position?.options?.fontSize || 12); const fontSize = parseInt(position?.options?.fontSize || 12);
const widgetTypeExist = [ const isTextTypeWidget = [
textWidget, textWidget,
textInputWidget, textInputWidget,
cellsWidget,
"name", "name",
"company", "company",
"job title", "job title",
@@ -1678,36 +1660,48 @@ export const multiSignEmbed = async (
"email" "email"
].includes(position.type); ].includes(position.type);
if (position.type === "checkbox") { if (position.type === "checkbox") {
let checkboxGapFromTop, isCheck; // Determine layout mode: 'vertical' (default) or 'horizontal'
let y = yPos(position) + 2; // const isHorizontal = position.layout === "horizontal";
//calculate checkbox size to draw on pdf const isHorizontal =
const checkboxSize = fontSize - 1; position?.options?.layout === "horizontal" ? true : false;
//calculate gap between checkbox and options // Initial “cursor” positions
const checkboxTextGapFromLeft = fontSize + 5; 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) { if (position?.options?.values.length > 0) {
position?.options?.values.forEach((item, ind) => { position.options.values.forEach((item, ind) => {
const checkboxRandomId = "checkbox" + randomId(); // 1. Advance the “cursor” on second+ iteration
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);
if (ind > 0) { if (ind > 0) {
y = y + checkboxGapFromTop; if (isHorizontal) {
} else { currentX += horizontalGap;
checkboxGapFromTop = fontSize + 3.2; } 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) { // 3. Draw the label (if labels are not hidden)
// below line of code is used to embed label with radio button in pdf if (!position.options.isHideLabel) {
const labelX = currentX + checkboxTextGapFromLeft;
const labelY = currentY - 3;
const optionsPosition = compensateRotation( const optionsPosition = compensateRotation(
page.getRotation().angle, page.getRotation().angle,
xPos(position) + checkboxTextGapFromLeft, labelX,
y - 3, labelY,
1, 1,
getSize, getSize,
fontSize, fontSize,
@@ -1717,98 +1711,134 @@ export const multiSignEmbed = async (
); );
page.drawText(item, optionsPosition); page.drawText(item, optionsPosition);
} }
// 4. Create and place the actual checkbox
const checkboxRandomId = "checkbox" + randomId();
const checkbox = form.createCheckBox(checkboxRandomId);
let checkboxObj = { let checkboxObj = {
x: xPos(position), x: currentX,
y: y, y: currentY,
width: checkboxSize, width: checkboxSize,
height: checkboxSize height: checkboxSize
}; };
checkboxObj = getWidgetPosition(page, checkboxObj, 1, getSize); checkboxObj = getWidgetPosition(page, checkboxObj, 1, getSize);
checkbox.addToPage(page, checkboxObj); checkbox.addToPage(page, checkboxObj);
// 5. Check or uncheck as needed, then make readonly
//applied which checkbox should be checked
if (isCheck) { if (isCheck) {
checkbox.check(); checkbox.check();
} else { } else {
checkbox.uncheck(); checkbox.uncheck();
} }
checkbox.enableReadOnly(); checkbox.enableReadOnly();
// 6. If horizontal layout, compute how far to shift next checkboxcircle
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) { } else if (isTextTypeWidget) {
let textContent; let textContent = "";
if (position?.options?.response) { if (position?.options?.response) {
textContent = position.options?.response; textContent = position.options?.response;
} else if (position?.options?.defaultValue) { } else if (position?.options?.defaultValue) {
textContent = position?.options?.defaultValue; textContent = position?.options?.defaultValue;
} }
const fixedWidth = widgetWidth; // Set your fixed width if (position.type === cellsWidget) {
const isNewOnEnterLineExist = textContent.includes("\n"); const cellCount =
position?.options?.cellCount || textContent.length || 1;
// Function to break text into lines based on the fixed width const charWidth = widgetWidth / cellCount;
const NewbreakTextIntoLines = (textContent, width) => { const y = yPos(position) - 4;
const lines = []; for (let i = 0; i < cellCount; i++) {
let currentLine = ""; const ch = textContent[i] || "";
const charX =
for (const word of textContent.split(" ")) { xPos(position) +
//get text line width charWidth * i +
const lineWidth = font.widthOfTextAtSize( (charWidth - font.widthOfTextAtSize(ch, fontSize)) / 2;
`${currentLine} ${word}`, const textPosition = compensateRotation(
fontSize page.getRotation().angle,
charX,
y,
1,
getSize,
fontSize,
updateColorInRgb,
font,
page
); );
//check text content line width is less or equal to container width if (ch) page.drawText(ch, textPosition);
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);
}
} }
} else {
const fixedWidth = widgetWidth; // Set your fixed width
const isNewOnEnterLineExist = textContent.includes("\n");
return lines; // Function to break text into lines based on the fixed width
}; const NewbreakTextIntoLines = (textContent, width) => {
//check if text content have `\n` string it means user press enter to go next line and handle condition const lines = [];
//else auto adjust text content according to container width let currentLine = "";
const lines = isNewOnEnterLineExist
? breakTextIntoLines(textContent, fixedWidth) for (const word of textContent.split(" ")) {
: NewbreakTextIntoLines(textContent, fixedWidth); //get text line width
// Set initial y-coordinate for the first line const lineWidth = font.widthOfTextAtSize(
let x = xPos(position); `${currentLine} ${word}`,
let y = yPos(position) - 4; fontSize
// Embed each line on the page );
for (const line of lines) { //check text content line width is less or equal to container width
const textPosition = compensateRotation( if (lineWidth <= width) {
page.getRotation().angle, currentLine += ` ${word}`;
x, } else {
y, lines.push(currentLine.trim());
1, currentLine = `${word}`;
getSize, }
fontSize, }
updateColorInRgb, lines.push(currentLine.trim());
font, return lines;
page };
); // Function to break text into lines based on when user go next line on press enter button
page.drawText(line, textPosition); const breakTextIntoLines = (textContent, width) => {
y += 18; // Adjust the line height as needed 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") { } else if (position.type === "dropdown") {
const dropdownRandomId = "dropdown" + randomId(); const dropdownRandomId = "dropdown" + randomId();
@@ -1848,33 +1878,47 @@ export const multiSignEmbed = async (
} else if (position.type === radioButtonWidget) { } else if (position.type === radioButtonWidget) {
const radioRandomId = "radio" + randomId(); const radioRandomId = "radio" + randomId();
const radioGroup = form.createRadioGroup(radioRandomId); 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 //getting radio buttons options text font size
const optionsFontSize = fontSize; const optionsFontSize = fontSize; // font size for option text
//calculate value of gap between radio button and options const radioTextGapFromLeft = fontSize + 6; // gap between circle and its label
const radioTextGapFromLeft = fontSize + 6; const radioSize = fontSize; // circle diameter (square of width×height)
//getting radio button font size // Initial “cursor” positions (from your existing helpers)
const radioSize = fontSize; let currentX = xPos(position) + 2;
//getting position of radio widget in y direction let currentY = yPos(position);
let y = yPos(position); // Vertical gap between two radiorows
//on the basic of option's length create radio button and message const verticalGap = fontSize + 6;
// Well 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) { 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) { if (ind > 0) {
y = y + radioButtonFromTop; if (isHorizontal) {
} else { // Move to the right by horizontalGap
radioButtonFromTop = fontSize + 6; currentX += horizontalGap;
} else {
// Move down by verticalGap (vertical stacking)
currentY += verticalGap;
}
} }
// 2. Draw the label text (if not hidden)
if (!position?.options?.isHideLabel) { 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( const optionsPosition = compensateRotation(
page.getRotation().angle, page.getRotation().angle,
xPos(position) + radioTextGapFromLeft, labelX,
y - 2, labelY,
1, 1,
getSize, getSize,
fontSize, optionsFontSize,
updateColorInRgb, updateColorInRgb,
font, font,
page page
@@ -1882,22 +1926,33 @@ export const multiSignEmbed = async (
page.drawText(item, optionsPosition); page.drawText(item, optionsPosition);
} }
// 3. Place the radiocircle itself at (currentX, currentY)
let radioObj = { let radioObj = {
x: xPos(position) + 2, x: currentX,
y: y, y: currentY,
width: radioSize, width: radioSize,
height: radioSize height: radioSize
}; };
radioObj = getWidgetPosition(page, radioObj, 1, getSize); radioObj = getWidgetPosition(page, radioObj, 1, getSize);
radioGroup.addOptionToPage(item, page, radioObj); 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. Preselect a value if provided
if (position?.options?.response) { if (position?.options?.response) {
radioGroup.select(position.options?.response); radioGroup.select(position.options?.response);
} else if (position?.options?.defaultValue) { } else if (position?.options?.defaultValue) {
radioGroup.select(position?.options?.defaultValue); radioGroup.select(position?.options?.defaultValue);
} }
// 6. Set to readonly (if required)
radioGroup.enableReadOnly(); radioGroup.enableReadOnly();
} else { } else {
const signature = { const signature = {
@@ -3180,6 +3235,9 @@ export const checkRegularExpress = (validateType, setValidatePlaceholder) => {
case "text": case "text":
setValidatePlaceholder("please enter text"); setValidatePlaceholder("please enter text");
break; break;
case "ssn":
setValidatePlaceholder("123-45-6789");
break;
default: default:
setValidatePlaceholder("please enter value"); setValidatePlaceholder("please enter value");
} }
+8
View File
@@ -186,6 +186,14 @@ export default function reportJson(id) {
btnIcon: "fa-light fa-envelope", btnIcon: "fa-light fa-envelope",
redirectUrl: "", redirectUrl: "",
action: "saveastemplate" action: "saveastemplate"
},
{
btnId: "8440",
btnLabel: "Fix & resend",
hoverLabel: "Fix & resend",
btnIcon: "fa-light fa-paper-plane",
redirectUrl: "",
action: "recreatedocument"
} }
] ]
} }
+28 -63
View File
@@ -47,7 +47,7 @@ import {
mailTemplate, mailTemplate,
updateDateWidgetsRes, updateDateWidgetsRes,
widgetDataValue, widgetDataValue,
getOriginalWH getOriginalWH,
} from "../constant/Utils"; } from "../constant/Utils";
import Header from "../components/pdf/PdfHeader"; import Header from "../components/pdf/PdfHeader";
import RenderPdf from "../components/pdf/RenderPdf"; import RenderPdf from "../components/pdf/RenderPdf";
@@ -159,6 +159,9 @@ function PdfRequestFiles(
const [assignedWidgetId, setAssignedWidgetId] = useState([]); const [assignedWidgetId, setAssignedWidgetId] = useState([]);
const [showSignPagenumber, setShowSignPagenumber] = useState([]); const [showSignPagenumber, setShowSignPagenumber] = useState([]);
const [owner, setOwner] = useState({}); const [owner, setOwner] = useState({});
const [currentIndex, setCurrentIndex] = useState(0);
const [formData, setFormData] = useState([]);
const [vacantRole, setVacantRole] = useState();
const [, drop] = useDrop({ const [, drop] = useDrop({
accept: "BOX", accept: "BOX",
drop: (item, monitor) => addPositionOfSignature(item, monitor), drop: (item, monitor) => addPositionOfSignature(item, monitor),
@@ -624,6 +627,7 @@ function PdfRequestFiles(
} catch (err) { } catch (err) {
console.log("err in get email verification ", err); console.log("err in get email verification ", err);
setHandleError(t("something-went-wrong-mssg")); setHandleError(t("something-went-wrong-mssg"));
setIsUiLoading(false);
} }
} }
//check if isEmailVerified then go on next step //check if isEmailVerified then go on next step
@@ -634,7 +638,6 @@ function PdfRequestFiles(
); );
if (checkUser && checkUser.length > 0) { if (checkUser && checkUser.length > 0) {
let checkboxExist, let checkboxExist,
requiredRadio,
showAlert = false, showAlert = false,
widgetKey, widgetKey,
radioExist, radioExist,
@@ -705,39 +708,12 @@ function PdfRequestFiles(
} }
} }
} }
//condition to check radio widget exist or not //else condition to check all type widget data fill or not except checkbox
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 { else {
//get all required type widgets except checkbox and radio //get all required type widgets except checkbox and radio
const requiredWidgets = checkUser[0].placeHolder[i].pos.filter( const requiredWidgets = checkUser[0].placeHolder[i].pos.filter(
(position) => (position) =>
position.options?.status === "required" && position.options?.status === "required" &&
position.type !== radioButtonWidget &&
position.type !== "checkbox" position.type !== "checkbox"
); );
if (requiredWidgets && requiredWidgets?.length > 0) { if (requiredWidgets && requiredWidgets?.length > 0) {
@@ -745,16 +721,13 @@ function PdfRequestFiles(
for (let i = 0; i < requiredWidgets?.length; i++) { for (let i = 0; i < requiredWidgets?.length; i++) {
checkSigned = requiredWidgets[i]?.options?.response; checkSigned = requiredWidgets[i]?.options?.response;
if (!checkSigned) { if (!checkSigned) {
const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl; let checkDefaultSigned =
if (!checkSignUrl) { requiredWidgets[i]?.options?.defaultValue;
let checkDefaultSigned = if (!checkDefaultSigned && !showAlert) {
requiredWidgets[i]?.options?.defaultValue; showAlert = true;
if (!checkDefaultSigned && !showAlert) { widgetKey = requiredWidgets[i].key;
showAlert = true; TourPageNumber = updatePage;
widgetKey = requiredWidgets[i].key; setminRequiredCount(null);
TourPageNumber = updatePage;
setminRequiredCount(null);
}
} }
} }
} }
@@ -766,20 +739,12 @@ function PdfRequestFiles(
break; break;
} }
} }
if (checkboxExist && requiredCheckbox && showAlert) { if (showAlert) {
setUnSignedWidgetId(widgetKey);
setPageNumber(TourPageNumber);
setWidgetsTour(true);
} else if (radioExist && showAlert) {
setUnSignedWidgetId(widgetKey);
setPageNumber(TourPageNumber);
setWidgetsTour(true);
} else if (showAlert) {
setUnSignedWidgetId(widgetKey); setUnSignedWidgetId(widgetKey);
setPageNumber(TourPageNumber); setPageNumber(TourPageNumber);
setWidgetsTour(true); setWidgetsTour(true);
setIsUiLoading(false);
} else { } else {
setIsUiLoading(true);
// `widgets` is Used to return widgets details with page number of current user // `widgets` is Used to return widgets details with page number of current user
const widgets = checkUser?.[0]?.placeHolder; const widgets = checkUser?.[0]?.placeHolder;
let pdfArrBuffer; let pdfArrBuffer;
@@ -855,16 +820,19 @@ function PdfRequestFiles(
isSuccessRoute, isSuccessRoute,
contactId contactId
); );
const index = pdfDetails?.[0]?.Signers.findIndex( const index =
(x) => x.objectId === signerObjectId updatedDoc.updatedPdfDetails?.[0]?.Signers.findIndex(
); (x) => x.objectId === contactId
);
const newIndex = index + 1; const newIndex = index + 1;
const usermail = { const usermail = {
Email: pdfDetails?.[0]?.Placeholders[newIndex]?.email || "" Email:
updatedDoc.updatedPdfDetails?.[0]?.Placeholders[newIndex]
?.email || ""
}; };
const user = usermail?.Email const user = usermail?.Email
? usermail ? usermail
: pdfDetails?.[0]?.Signers[newIndex]; : updatedDoc.updatedPdfDetails?.[0]?.Signers[newIndex];
if ( if (
sendmail !== "false" && sendmail !== "false" &&
sendInOrder sendInOrder
@@ -1023,6 +991,7 @@ function PdfRequestFiles(
isShow: true, isShow: true,
alertMessage: t("something-went-wrong-mssg") alertMessage: t("something-went-wrong-mssg")
}); });
setIsUiLoading(false);
} }
} catch (err) { } catch (err) {
console.log("err in embedsign", err); console.log("err in embedsign", err);
@@ -1036,6 +1005,7 @@ function PdfRequestFiles(
} }
const handleSignPdf = async () => { const handleSignPdf = async () => {
setIsUiLoading(true);
await embedWidgetsData(); await embedWidgetsData();
}; };
@@ -1254,7 +1224,8 @@ function PdfRequestFiles(
setRequestSignTour(true); setRequestSignTour(true);
if (isDontShow) { if (isDontShow) {
const isEnableOTP = pdfDetails?.[0]?.IsEnableOTP || false; const isEnableOTP = pdfDetails?.[0]?.IsEnableOTP || false;
if (!isEnableOTP) { const sessionToken = localStorage.getItem("accesstoken");
if (!isEnableOTP && !sessionToken) {
try { try {
await axios.post( await axios.post(
`${localStorage.getItem("baseUrl")}functions/updatecontacttour`, `${localStorage.getItem("baseUrl")}functions/updatecontacttour`,
@@ -1651,12 +1622,6 @@ function PdfRequestFiles(
); );
setSignerPos(updatesignerPos); setSignerPos(updatesignerPos);
} }
// if (dragTypeValue === "dropdown") {
// setShowDropdown(true);
// } else if (dragTypeValue === "checkbox") {
// setIsCheckbox(true);
// } else
if ( if (
[textWidget, "name", "company", "job title", "email"].includes( [textWidget, "name", "company", "job title", "email"].includes(
dragTypeValue dragTypeValue
@@ -1756,7 +1721,6 @@ function PdfRequestFiles(
setShowSignPagenumber(sortedPagenumber); setShowSignPagenumber(sortedPagenumber);
} }
}; };
return ( return (
<DndProvider backend={HTML5Backend}> <DndProvider backend={HTML5Backend}>
<Title <Title
@@ -1828,6 +1792,7 @@ function PdfRequestFiles(
{!requestSignTour && {!requestSignTour &&
isAgree && isAgree &&
signerObjectId && signerObjectId &&
!alreadySign &&
requestSignTourFunction()} requestSignTourFunction()}
<Tour <Tour
showNumber={false} showNumber={false}
+35 -1
View File
@@ -27,6 +27,7 @@ import {
multiSignEmbed, multiSignEmbed,
addWidgetOptions, addWidgetOptions,
textInputWidget, textInputWidget,
cellsWidget,
textWidget, textWidget,
radioButtonWidget, radioButtonWidget,
color, color,
@@ -1417,7 +1418,8 @@ function PlaceHolderSign() {
deleteOption, deleteOption,
status, status,
defaultValue, defaultValue,
isHideLabel isHideLabel,
layout
) => { ) => {
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId); const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
if (filterSignerPos.length > 0) { if (filterSignerPos.length > 0) {
@@ -1453,6 +1455,7 @@ function PlaceHolderSign() {
name: dropdownName, name: dropdownName,
values: dropdownOptions, values: dropdownOptions,
status: status, status: status,
layout: layout,
isReadOnly: isReadOnly || false, isReadOnly: isReadOnly || false,
isHideLabel: isHideLabel || false, isHideLabel: isHideLabel || false,
defaultValue: defaultValue, defaultValue: defaultValue,
@@ -1492,6 +1495,7 @@ function PlaceHolderSign() {
maxRequiredCount: maxCount maxRequiredCount: maxCount
}, },
defaultValue: defaultValue, defaultValue: defaultValue,
layout: layout,
isReadOnly: isReadOnly || false, isReadOnly: isReadOnly || false,
isHideLabel: isHideLabel || false, isHideLabel: isHideLabel || false,
fontSize: fontSize:
@@ -1590,6 +1594,36 @@ function PlaceHolderSign() {
isReadOnly: defaultdata?.isReadOnly || false 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)) { } else if (["signature"].includes(position.type)) {
return { return {
...position, ...position,
+55 -4
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState, useRef } from "react";
import ReportTable from "../primitives/GetReportDisplay"; import ReportTable from "../primitives/GetReportDisplay";
import Parse from "parse"; import Parse from "parse";
import axios from "axios"; import axios from "axios";
@@ -27,12 +27,17 @@ const Report = () => {
const [isImport, setIsImport] = useState(false); const [isImport, setIsImport] = useState(false);
const abortController = new AbortController(); const abortController = new AbortController();
const docPerPage = 10; const docPerPage = 10;
const [searchTerm, setSearchTerm] = useState("");
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
const debounceTimer = useRef(null);
// below useEffect is call when id param change // below useEffect is call when id param change
useEffect(() => { useEffect(() => {
setReportName(""); setReportName("");
setList([]); setList([]);
getReportData(); setSearchTerm("");
setMobileSearchOpen(false);
getReportData(0, docPerPage, "");
// Function returned from useEffect is called on unmount // Function returned from useEffect is called on unmount
return () => { return () => {
@@ -48,7 +53,7 @@ const Report = () => {
// below useEffect call when isNextRecord state is true and fetch next record // below useEffect call when isNextRecord state is true and fetch next record
useEffect(() => { useEffect(() => {
if (isNextRecord) { if (isNextRecord) {
getReportData(List.length, 20); getReportData(List.length, 20, searchTerm);
} }
// eslint-disable-next-line // eslint-disable-next-line
}, [isNextRecord]); }, [isNextRecord]);
@@ -56,7 +61,46 @@ const Report = () => {
const handleDontShow = (isChecked) => { const handleDontShow = (isChecked) => {
setIsDontShow(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); // setIsLoader(true);
const json = reportJson(id); const json = reportJson(id);
if (json) { if (json) {
@@ -77,6 +121,9 @@ const Report = () => {
const skipRecord = id === "4Hhwbp482K" ? 0 : skipUserRecord; const skipRecord = id === "4Hhwbp482K" ? 0 : skipUserRecord;
const limitRecord = id === "4Hhwbp482K" ? 200 : limit; const limitRecord = id === "4Hhwbp482K" ? 200 : limit;
const params = { reportId: id, skip: skipRecord, limit: limitRecord }; const params = { reportId: id, skip: skipRecord, limit: limitRecord };
if (term) {
params.searchTerm = term;
}
const url = `${localStorage.getItem("baseUrl")}functions/getReport`; const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
const res = await axios.post(url, params, { const res = await axios.post(url, params, {
headers: headers, headers: headers,
@@ -197,6 +244,10 @@ const Report = () => {
report_help={reporthelp} report_help={reporthelp}
tourData={tourData} tourData={tourData}
isDontShow={isDontShow} isDontShow={isDontShow}
mobileSearchOpen={mobileSearchOpen}
setMobileSearchOpen={setMobileSearchOpen}
searchTerm={searchTerm}
handleSearchChange={handleSearchChange}
isImport={isImport} isImport={isImport}
/> />
) : ( ) : (
+28 -4
View File
@@ -22,6 +22,7 @@ import {
randomId, randomId,
getDate, getDate,
textWidget, textWidget,
cellsWidget,
convertPdfArrayBuffer, convertPdfArrayBuffer,
textInputWidget, textInputWidget,
fetchImageBase64, fetchImageBase64,
@@ -356,9 +357,13 @@ function SignYourSelf() {
); );
const dragTypeValue = item?.text ? item.text : monitor.type; const dragTypeValue = item?.text ? item.text : monitor.type;
const widgetValue = getWidgetValue(dragTypeValue); const widgetValue = getWidgetValue(dragTypeValue);
const widgetTypeExist = ["name", "company", "job title", "email"].includes( const widgetTypeExist = [
dragTypeValue "name",
); "company",
"job title",
"email",
cellsWidget
].includes(dragTypeValue);
const containerScale = getContainerScale( const containerScale = getContainerScale(
pdfOriginalWH, pdfOriginalWH,
pageNumber, pageNumber,
@@ -439,6 +444,7 @@ function SignYourSelf() {
[ [
textInputWidget, textInputWidget,
textWidget, textWidget,
cellsWidget,
"name", "name",
"company", "company",
"job title", "job title",
@@ -931,7 +937,8 @@ function SignYourSelf() {
deleteOption, deleteOption,
status, status,
defaultValue, defaultValue,
isHideLabel isHideLabel,
layout
) => { ) => {
const getPageNumer = xyPosition.filter( const getPageNumer = xyPosition.filter(
(data) => data.pageNumber === pageNumber (data) => data.pageNumber === pageNumber
@@ -939,6 +946,8 @@ function SignYourSelf() {
if (getPageNumer.length > 0) { if (getPageNumer.length > 0) {
const getXYdata = getPageNumer[0].pos; const getXYdata = getPageNumer[0].pos;
const getPosData = getXYdata; const getPosData = getXYdata;
const widgetLayout =
currWidgetsDetails?.type === "checkbox" ? { layout: layout } : {};
const addSignPos = getPosData.map((position) => { const addSignPos = getPosData.map((position) => {
if (position.key === currWidgetsDetails?.key) { if (position.key === currWidgetsDetails?.key) {
if (addOption) { if (addOption) {
@@ -962,6 +971,7 @@ function SignYourSelf() {
...position.options, ...position.options,
name: dropdownName, name: dropdownName,
values: dropdownOptions, values: dropdownOptions,
...widgetLayout,
isReadOnly: isReadOnly, isReadOnly: isReadOnly,
isHideLabel: isHideLabel || false, isHideLabel: isHideLabel || false,
fontSize: fontSize:
@@ -1028,6 +1038,19 @@ function SignYourSelf() {
handleTextSettingModal(false); 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 = () => { const clickOnZoomIn = () => {
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent); onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
}; };
@@ -1289,6 +1312,7 @@ function SignYourSelf() {
pdfBase64Url={pdfBase64Url} pdfBase64Url={pdfBase64Url}
fontSize={fontSize} fontSize={fontSize}
setFontSize={setFontSize} setFontSize={setFontSize}
setCellCount={setCellCount}
fontColor={fontColor} fontColor={fontColor}
setFontColor={setFontColor} setFontColor={setFontColor}
isResize={isResize} isResize={isResize}
@@ -1,4 +1,4 @@
import React, { useEffect, useState, useRef } from "react"; import { useEffect, useState, useRef } from "react";
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage"; import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import { useParams, useNavigate } from "react-router"; import { useParams, useNavigate } from "react-router";
import axios from "axios"; import axios from "axios";
@@ -22,6 +22,7 @@ import {
defaultWidthHeight, defaultWidthHeight,
addWidgetOptions, addWidgetOptions,
textInputWidget, textInputWidget,
cellsWidget,
radioButtonWidget, radioButtonWidget,
getContainerScale, getContainerScale,
convertBase64ToFile, convertBase64ToFile,
@@ -1274,7 +1275,8 @@ const TemplatePlaceholder = () => {
deleteOption, deleteOption,
status, status,
defaultValue, defaultValue,
isHideLabel isHideLabel,
layout
) => { ) => {
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId); const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
if (filterSignerPos.length > 0) { if (filterSignerPos.length > 0) {
@@ -1313,6 +1315,7 @@ const TemplatePlaceholder = () => {
name: dropdownName, name: dropdownName,
values: dropdownOptions, values: dropdownOptions,
status: status, status: status,
layout: layout,
defaultValue: defaultValue, defaultValue: defaultValue,
isReadOnly: isReadOnly || false, isReadOnly: isReadOnly || false,
isHideLabel: isHideLabel || false, isHideLabel: isHideLabel || false,
@@ -1351,6 +1354,7 @@ const TemplatePlaceholder = () => {
minRequiredCount: minCount, minRequiredCount: minCount,
maxRequiredCount: maxCount maxRequiredCount: maxCount
}, },
layout: layout,
isReadOnly: isReadOnly || false, isReadOnly: isReadOnly || false,
defaultValue: defaultValue, defaultValue: defaultValue,
isHideLabel: isHideLabel || false, isHideLabel: isHideLabel || false,
@@ -1451,6 +1455,36 @@ const TemplatePlaceholder = () => {
"black" "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)) { } else if (["signature"].includes(position.type)) {
return { return {
...position, ...position,
@@ -1510,6 +1544,22 @@ const TemplatePlaceholder = () => {
setIsRadio(false); setIsRadio(false);
setIsCheckbox(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 = () => { const clickOnZoomIn = () => {
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent); onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
@@ -1828,6 +1878,7 @@ const TemplatePlaceholder = () => {
pdfBase64Url={pdfBase64Url} pdfBase64Url={pdfBase64Url}
fontSize={fontSize} fontSize={fontSize}
setFontSize={setFontSize} setFontSize={setFontSize}
setCellCount={setCellCount}
fontColor={fontColor} fontColor={fontColor}
setFontColor={setFontColor} setFontColor={setFontColor}
isResize={isResize} isResize={isResize}
@@ -7,6 +7,7 @@ import ModalUi from "./ModalUi";
import AddSigner from "../components/AddSigner"; import AddSigner from "../components/AddSigner";
import { import {
emailRegex, emailRegex,
iconColor,
} from "../constant/const"; } from "../constant/const";
import Alert from "./Alert"; import Alert from "./Alert";
import Tooltip from "./Tooltip"; import Tooltip from "./Tooltip";
@@ -907,7 +908,6 @@ const ReportTable = (props) => {
setActLoader({}); setActLoader({});
} }
}; };
const handleUpdateExpiry = async (e, item) => { const handleUpdateExpiry = async (e, item) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -1314,7 +1314,6 @@ const ReportTable = (props) => {
try { try {
const params = { docId: doc?.objectId }; const params = { docId: doc?.objectId };
const templateRes = await Parse.Cloud.run("saveastemplate", params); const templateRes = await Parse.Cloud.run("saveastemplate", params);
// console.log("templateRes ", templateRes);
setTemplateId(templateRes?.id); setTemplateId(templateRes?.id);
setIsSuccess({ [doc.objectId]: true }); setIsSuccess({ [doc.objectId]: true });
} catch (err) { } catch (err) {
@@ -1394,6 +1393,12 @@ const ReportTable = (props) => {
setActLoader({}); setActLoader({});
} }
}; };
const restrictBtn = (item, act) => {
return item.IsSignyourself && act.action === "recreatedocument"
? true
: false;
};
return ( return (
<div className="relative"> <div className="relative">
{Object.keys(actLoader)?.length > 0 && ( {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]" className="cursor-pointer fa-light fa-square-plus text-accent text-[30px] md:text-[35px]"
></i> ></i>
)} )}
{/* Search input */}
<div className="hidden md:block p-2">
<input
type="search"
value={props.searchTerm}
onChange={props.handleSearchChange}
placeholder={t('search.documents')}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-64 text-xs"
/>
</div>
<button
className="md:hidden p-2 flex justify-center items-center focus:outline-none rounded-md hover:bg-gray-200 text-[18px]"
aria-label="Search"
onClick={() => props.setMobileSearchOpen(!props.mobileSearchOpen)}
>
<i style={{ color: `${iconColor}` }} className="fa-solid fa-magnifying-glass"></i>
</button>
<ModalUi <ModalUi
isOpen={isModal?.export} isOpen={isModal?.export}
title={t("bulk-import")} title={t("bulk-import")}
@@ -1536,6 +1558,17 @@ const ReportTable = (props) => {
</ModalUi> </ModalUi>
</div> </div>
</div> </div>
{props.mobileSearchOpen && (
<div className="top-full left-0 w-full bg-white px-4 py-2 shadow-md md:hidden">
<input
type="search"
value={props.searchTerm}
onChange={props.handleSearchChange}
placeholder="Search documents…"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
)}
<div <div
className={`overflow-auto w-full border-b ${ className={`overflow-auto w-full border-b ${
props.List?.length > 0 props.List?.length > 0
@@ -1837,33 +1870,39 @@ const ReportTable = (props) => {
{isOption[item.objectId] && {isOption[item.objectId] &&
act.action === "option" && ( act.action === "option" && (
<ul className="absolute -right-1 top-auto z-[70] w-max op-dropdown-content op-menu shadow-black/20 shadow bg-base-100 text-base-content rounded-box"> <ul className="absolute -right-1 top-auto z-[70] w-max op-dropdown-content op-menu shadow-black/20 shadow bg-base-100 text-base-content rounded-box">
{act.subaction?.map((subact) => ( {act.subaction?.map(
<li (subact) =>
key={subact.btnId} !restrictBtn(
onClick={() => item,
handleActionBtn( subact
subact, ) && (
item <li
) key={subact.btnId}
} onClick={() =>
title={t( handleActionBtn(
`btnLabel.${subact.hoverLabel}` subact,
)} item
> )
<span> }
<i title={t(
className={`${subact.btnIcon} mr-1.5`} `btnLabel.${subact.hoverLabel}`
></i> )}
{subact.btnLabel && ( >
<span className="text-[13px] capitalize font-medium"> <span>
{t( <i
`btnLabel.${subact.btnLabel}` className={`${subact.btnIcon} mr-1.5`}
></i>
{subact.btnLabel && (
<span className="text-[13px] capitalize font-medium">
{t(
`btnLabel.${subact.btnLabel}`
)}
</span>
)} )}
</span> </span>
)} </li>
</span> )
</li> )}
))}
</ul> </ul>
)} )}
</div> </div>
@@ -15,8 +15,6 @@ async function deductcount(docsCount, extUserId) {
async function sendMail(document, publicUrl) { async function sendMail(document, publicUrl) {
//sessionToken //sessionToken
const baseUrl = new URL(publicUrl); const baseUrl = new URL(publicUrl);
// console.log("pdfDetails", pdfDetails);
const timeToCompleteDays = document?.TimeToCompleteDays || 15; const timeToCompleteDays = document?.TimeToCompleteDays || 15;
const ExpireDate = new Date(document.createdAt); const ExpireDate = new Date(document.createdAt);
ExpireDate.setDate(ExpireDate.getDate() + timeToCompleteDays); ExpireDate.setDate(ExpireDate.getDate() + timeToCompleteDays);
@@ -168,9 +166,9 @@ async function batchQuery(userId, Documents, Ip, parseConfig, type, publicUrl) {
})), })),
ACL: Acl, ACL: Acl,
SentToOthers: true, SentToOthers: true,
RemindOnceInEvery: x.RemindOnceInEvery || 5, RemindOnceInEvery: x.RemindOnceInEvery ? parseInt(x.RemindOnceInEvery) : 5,
AutomaticReminders: x.AutomaticReminders || false, AutomaticReminders: x.AutomaticReminders || false,
TimeToCompleteDays: x.TimeToCompleteDays || 15, TimeToCompleteDays: x.TimeToCompleteDays ? parseInt(x.TimeToCompleteDays) : 15,
OriginIp: Ip, OriginIp: Ip,
DocSentAt: { __type: 'Date', iso: isoDate }, DocSentAt: { __type: 'Date', iso: isoDate },
IsEnableOTP: x?.IsEnableOTP || false, IsEnableOTP: x?.IsEnableOTP || false,
@@ -231,6 +229,7 @@ export default async function createBatchDocs(request) {
const sessionToken = request.headers?.sessiontoken; const sessionToken = request.headers?.sessiontoken;
const type = request.headers?.type || 'quicksend'; const type = request.headers?.type || 'quicksend';
const Documents = JSON.parse(strDocuments); const Documents = JSON.parse(strDocuments);
const Ip = request?.headers?.['x-real-ip'] || ''; const Ip = request?.headers?.['x-real-ip'] || '';
// Access the host from the headers // Access the host from the headers
const publicUrl = request.headers.public_url; const publicUrl = request.headers.public_url;
@@ -2,19 +2,26 @@ import { cloudServerUrl } from '../../Utils.js';
import reportJson from './reportsJson.js'; import reportJson from './reportsJson.js';
import axios from 'axios'; 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) { export default async function getReport(request) {
const reportId = request.params.reportId; const reportId = request.params.reportId;
const limit = request.params.limit; const limit = request.params.limit;
const skip = request.params.skip; const skip = request.params.skip;
const searchTerm = request.params.searchTerm || '';
const serverUrl = cloudServerUrl; //process.env.SERVER_URL; const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const appId = process.env.APP_ID; const appId = process.env.APP_ID;
const masterKey = process.env.MASTER_KEY; const masterKey = process.env.MASTER_KEY;
const sessionToken = request.headers['sessiontoken'] || request.headers['x-parse-session-token'];
try { try {
const userRes = await axios.get(serverUrl + '/users/me', { const userRes = await axios.get(serverUrl + '/users/me', {
headers: { headers: {
'X-Parse-Application-Id': appId, 'X-Parse-Application-Id': appId,
'X-Parse-Session-Token': request.headers['sessiontoken'], 'X-Parse-Session-Token': sessionToken,
}, },
}); });
const userId = userRes.data && userRes.data.objectId; const userId = userRes.data && userRes.data.objectId;
@@ -25,7 +32,7 @@ export default async function getReport(request) {
const { params, keys } = json; const { params, keys } = json;
const orderBy = '-updatedAt'; const orderBy = '-updatedAt';
const strKeys = keys.join(); const strKeys = keys.join();
let strParams = JSON.stringify(params); let paramsObj = { ...params };
if (reportId == '6TeaPr321t') { if (reportId == '6TeaPr321t') {
const extUserQuery = new Parse.Query('contracts_Users'); const extUserQuery = new Parse.Query('contracts_Users');
extUserQuery.equalTo('Email', userRes.data.email); extUserQuery.equalTo('Email', userRes.data.email);
@@ -36,8 +43,8 @@ export default async function getReport(request) {
if (_extUser?.TeamIds && _extUser.TeamIds?.length > 0) { if (_extUser?.TeamIds && _extUser.TeamIds?.length > 0) {
let teamArr = []; let teamArr = [];
_extUser?.TeamIds?.forEach(x => (teamArr = [...teamArr, ...x.Ancestors])); _extUser?.TeamIds?.forEach(x => (teamArr = [...teamArr, ...x.Ancestors]));
strParams = JSON.stringify({ paramsObj = {
...params, ...paramsObj,
$or: [ $or: [
{ SharedWith: { $in: teamArr } }, { SharedWith: { $in: teamArr } },
{ {
@@ -55,15 +62,23 @@ export default async function getReport(request) {
}, },
}, },
], ],
}); };
} else { } else {
strParams = JSON.stringify({ paramsObj = {
...params, ...paramsObj,
CreatedBy: { __type: 'Pointer', className: '_User', objectId: userId }, 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 = { const headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Parse-Application-Id': appId, 'X-Parse-Application-Id': appId,
@@ -14,11 +14,14 @@ export default async function recreateDocument(request) {
if (!doc) { if (!doc) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found'); 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 _docRes = doc?.toJSON();
const { objectId, SignedUrl, AuditTrail, ACL, DeclineBy, DeclineReason, ...docRes } = _docRes; const { objectId, SignedUrl, AuditTrail, ACL, DeclineBy, DeclineReason, ...docRes } = _docRes;
const createDoc = new Parse.Object('contracts_Document'); const createDoc = new Parse.Object('contracts_Document');
Object.entries(docRes).forEach(([key, value]) => { Object.entries(docRes).forEach(([key, value]) => {
if (key === 'IsDeclined') { if (key === 'IsDeclined' || key === 'IsCompleted') {
createDoc.set(key, false); createDoc.set(key, false);
} else { } else {
createDoc.set(key, value); createDoc.set(key, value);
@@ -123,6 +123,8 @@ export default function reportJson(id, userId) {
'TimeToCompleteDays', 'TimeToCompleteDays',
'IsSignyourself', 'IsSignyourself',
'IsCompleted', 'IsCompleted',
'ExpiryDate',
'IsSignyourself',
], ],
}; };
// declined documents report // declined documents report
@@ -53,10 +53,16 @@ const makeEmail = async (
const isSecure = const isSecure =
new URL(url)?.protocol === 'https:' && new URL(url)?.hostname !== 'localhost'; new URL(url)?.protocol === 'https:' && new URL(url)?.hostname !== 'localhost';
if (isSecure) { if (isSecure) {
https.get(url, async function (response) { https
response.pipe(Pdf); .get(url, async function (response) {
response.on('end', () => resolve('success')); response.pipe(Pdf);
}); Pdf.on('finish', () => resolve('success'));
Pdf.on('error', () => resolve('error'));
})
.on('error', e => {
console.error(`error: ${e.message}`);
resolve('error');
});
} else { } else {
const httpsAgent = new https.Agent({ rejectUnauthorized: false }); // Disable SSL validation const httpsAgent = new https.Agent({ rejectUnauthorized: false }); // Disable SSL validation
axios axios