Merge pull request 'template&assessment-self-sign-first-form' (#14) from jai into main
Reviewed-on: http://git.hicglobalsolutions.in/BhawanSingh/SME-Web/pulls/14
This commit is contained in:
commit
d3b4ee61c9
|
|
@ -204,7 +204,7 @@ const Cards: React.FC<CardsProps> = ({ type = 'dashboard', onOpenModal, onDateRa
|
|||
// }
|
||||
|
||||
return (
|
||||
<div className="w-full px-4 py-3 mt-2 rounded-md">
|
||||
<div className="w-full px-3 rounded-md">
|
||||
{/* Header */}
|
||||
<div className="flex flex-col lg:flex-row items-start lg:items-center justify-between mb-2 gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
|
|
@ -212,7 +212,7 @@ const Cards: React.FC<CardsProps> = ({ type = 'dashboard', onOpenModal, onDateRa
|
|||
<FaFilter className="text-white text-lg" />
|
||||
</div> */}
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-gray-800">Overview</h2>
|
||||
{/* <h2 className="text-lg font-semibold text-gray-800">Overview</h2> */}
|
||||
{/* <p className="text-gray-600 text-sm">Monitor performance metrics</p> */}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -231,11 +231,11 @@ const Cards: React.FC<CardsProps> = ({ type = 'dashboard', onOpenModal, onDateRa
|
|||
</div>
|
||||
</div>
|
||||
</div> :
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4">
|
||||
<div className="flex flex-wrap gap-2 justify-between">
|
||||
{cards.map((card, index) => (
|
||||
<div
|
||||
key={card.title}
|
||||
className={`group relative overflow-hidden rounded-xl ${card.gradient} border border-white/40 p-4 transition-all duration-300 hover:scale-105 hover:shadow-lg ${card.shadowColor} cursor-pointer backdrop-blur-sm`}
|
||||
className={`group relative overflow-hidden rounded-lg ${card.gradient} border border-white/40 px-3 py-1 transition-all duration-300 hover:scale-105 hover:shadow-lg ${card.shadowColor} cursor-pointer backdrop-blur-sm min-w-[200px] max-w-[220px]`}
|
||||
style={{
|
||||
animationDelay: `${index * 100}ms`
|
||||
}}
|
||||
|
|
@ -247,24 +247,20 @@ const Cards: React.FC<CardsProps> = ({ type = 'dashboard', onOpenModal, onDateRa
|
|||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative z-10">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="relative flex justify-between items-center z-10">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`p-2 rounded-lg border ${card.iconBg} group-hover:scale-110 transition-transform duration-200`}>
|
||||
{card.icon}
|
||||
</div>
|
||||
{renderTrendIndicator(card.trend, card.percentage)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-xs font-medium text-gray-600 group-hover:text-gray-700 transition-colors">
|
||||
<h3 className="text-sm font-medium text-gray-600 group-hover:text-gray-700 transition-colors">
|
||||
{card.title}
|
||||
</h3>
|
||||
<div className="flex items-baseline gap-1">
|
||||
<p className={`text-xl font-bold ${card.textColor} group-hover:scale-105 transition-transform duration-200`}>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center">
|
||||
<p className={`text-xl font-bold ${card.textColor} group-hover:scale-105 transition-transform duration-200 leading-none mb-1`}>
|
||||
{card.count}
|
||||
</p>
|
||||
<span className="text-[10px] text-gray-500">items</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const Home = () => {
|
|||
{
|
||||
title: 'Action Needed',
|
||||
count: data.actionNeeded || 0,
|
||||
icon: <FaTasks size={18} className="text-indigo-600" />,
|
||||
icon: <FaTasks size={15} className="text-indigo-600" />,
|
||||
gradient: 'bg-gradient-to-br from-indigo-50 to-indigo-100',
|
||||
shadowColor: 'shadow-indigo-200/50',
|
||||
iconBg: 'bg-indigo-100 border-indigo-200',
|
||||
|
|
@ -43,7 +43,7 @@ const Home = () => {
|
|||
{
|
||||
title: 'Expiring Soon',
|
||||
count: data.expiring || 0,
|
||||
icon: <FaHourglassHalf size={18} className="text-rose-600" />,
|
||||
icon: <FaHourglassHalf size={15} className="text-rose-600" />,
|
||||
gradient: 'bg-gradient-to-br from-rose-50 to-rose-100',
|
||||
shadowColor: 'shadow-rose-200/50',
|
||||
iconBg: 'bg-rose-100 border-rose-200',
|
||||
|
|
@ -53,7 +53,7 @@ const Home = () => {
|
|||
{
|
||||
title: 'Completed',
|
||||
count: data.completed || 0,
|
||||
icon: <FaCheckCircle size={18} className="text-teal-600" />,
|
||||
icon: <FaCheckCircle size={15} className="text-teal-600" />,
|
||||
gradient: 'bg-gradient-to-br from-teal-50 to-teal-100',
|
||||
shadowColor: 'shadow-teal-200/50',
|
||||
iconBg: 'bg-teal-100 border-teal-200',
|
||||
|
|
@ -63,7 +63,7 @@ const Home = () => {
|
|||
{
|
||||
title: 'Draft',
|
||||
count: data.draft || 0,
|
||||
icon: <FaClock size={18} className="text-stone-600" />,
|
||||
icon: <FaClock size={15} className="text-stone-600" />,
|
||||
gradient: 'bg-gradient-to-br from-blue-50 to-blue-100',
|
||||
shadowColor: 'shadow-blue-200/50',
|
||||
iconBg: 'bg-blue-100 border-blue-200',
|
||||
|
|
|
|||
|
|
@ -7,15 +7,17 @@ import api from "../../../config/api";
|
|||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { RootState } from "../../../store/store";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
|
||||
import { setImages } from "../../../store/slices/selectedDocsSlice";
|
||||
import { addRecipient, addRecipientAsync, fetchRecipients, recipientType, resetRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE, updateRecipientIds } from "../../../store/slices/selectedRecipientsSlice";
|
||||
import { addRecipient, addRecipientAsync, fetchRecipients, recipientType, removeAllRecipients, removeRecipientAsync, resetRecipients, toggleToTemplates, TRANSACTION_TEMPLATE_TOGGLE, updateRecipientIds } from "../../../store/slices/selectedRecipientsSlice";
|
||||
import { setTransactionId, TRANSACTION_STATUS_ENUMS, updateTransactionStatus } from "../../../store/slices/transactionSlice";
|
||||
import { hideLoader, showLoader } from "../../../store/slices/loaderSlice";
|
||||
import Loader from "../../dashboard/Loader";
|
||||
import { Bounce, toast } from "react-toastify";
|
||||
import { fetchDocuments, resetDocuments, setDocuments, uploadDocuments } from "../../../store/slices/documentSlice";
|
||||
import { SignTypeEnum } from "../../../utlity/types";
|
||||
import { RiPencilFill } from "react-icons/ri";
|
||||
import SelfSignWarnModal from "./SelfSignWarnModal";
|
||||
|
||||
const ProcessStepOne = () => {
|
||||
interface EmailDtls {
|
||||
|
|
@ -28,6 +30,9 @@ const ProcessStepOne = () => {
|
|||
const signType = useParams<{ signType: string }>().signType;
|
||||
const templateId = useParams<{ templateId: string }>().templateId;
|
||||
let { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
|
||||
const [searchParams] = useSearchParams();
|
||||
console.log("searchParams", searchParams.get('useTemplate'))
|
||||
const isTamplate = searchParams.get('useTemplate')
|
||||
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
|
@ -54,10 +59,29 @@ const ProcessStepOne = () => {
|
|||
|
||||
const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
|
||||
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients)
|
||||
const [showSelfSignWarnModal, setShowSelfSignWarnModal] = useState(false)
|
||||
|
||||
console.log()
|
||||
const userData = useSelector((state: RootState) => state.user)
|
||||
// ProcessStepOne.tsx
|
||||
const topRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const [templateName, setTemplateName] = useState('Untitled');
|
||||
const [isTyping, setIsTyping] = useState(false);
|
||||
|
||||
// Optional: use a timer to detect pause in typing
|
||||
let typingTimeout = null;
|
||||
|
||||
const handleChange = (e) => {
|
||||
setTemplateName(e.target.value);
|
||||
setIsTyping(true);
|
||||
|
||||
clearTimeout(typingTimeout);
|
||||
typingTimeout = setTimeout(() => {
|
||||
setIsTyping(false);
|
||||
}, 5000); // hide icon while typing, show it again after 500ms of no input
|
||||
};
|
||||
|
||||
// const userData = useSelector((state: RootState) => state.user)
|
||||
|
||||
const [transactionDetails, setTransactionDetails] = useState<any>([]);
|
||||
|
|
@ -71,6 +95,7 @@ const ProcessStepOne = () => {
|
|||
try {
|
||||
const res = await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`);
|
||||
setTransactionDetails(res.data)
|
||||
// console.log("setTransactionDetails",res.data)
|
||||
} catch (e) {
|
||||
console.log("Error - transactions details")
|
||||
}
|
||||
|
|
@ -111,6 +136,7 @@ const ProcessStepOne = () => {
|
|||
subject: false
|
||||
}
|
||||
|
||||
if (transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES) {
|
||||
if (!selectedDocuments.length) {
|
||||
formError.documents = true
|
||||
}
|
||||
|
|
@ -143,6 +169,8 @@ const ProcessStepOne = () => {
|
|||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
dispatch(showLoader("Processing Documents..."));
|
||||
|
|
@ -193,6 +221,17 @@ const ProcessStepOne = () => {
|
|||
let recipientId = null;
|
||||
|
||||
if (signType === SignTypeEnum.selfSign) {
|
||||
dispatch(removeAllRecipients());
|
||||
await Promise.all(
|
||||
selectedRecipients.map((recipient) => dispatch(removeRecipientAsync({
|
||||
recipientId: recipient.id,
|
||||
email: recipient.email,
|
||||
accountId,
|
||||
transactionId,
|
||||
}))
|
||||
)
|
||||
);
|
||||
|
||||
recipientsDataTemp = [{
|
||||
id: null,
|
||||
routingOrder: 1,
|
||||
|
|
@ -232,6 +271,7 @@ const ProcessStepOne = () => {
|
|||
console.log(`1/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`)
|
||||
|
||||
let res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, {
|
||||
name: templateName,
|
||||
subject: emailDtls.subject, message: emailDtls.message,
|
||||
status: "CREATED"
|
||||
})
|
||||
|
|
@ -299,6 +339,7 @@ const ProcessStepOne = () => {
|
|||
const handleSelfSignCb = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (e.currentTarget.checked) {
|
||||
navigate("self_sign");
|
||||
setShowSelfSignWarnModal((prev) => !prev)
|
||||
} else {
|
||||
navigate("./../");
|
||||
}
|
||||
|
|
@ -317,7 +358,21 @@ const ProcessStepOne = () => {
|
|||
onClick={handleClose}
|
||||
className='text-2xl text-slate-500 cursor-pointer hover:text-[var(--primary)] transition'
|
||||
/>
|
||||
<p className="text-base font-normal text-gray-700 border-l pl-4">Add Recipient and Message</p>
|
||||
<p className="text-base font-normal text-gray-700 border-l pl-4 relative">
|
||||
{transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES ?
|
||||
<>
|
||||
<input
|
||||
value={templateName}
|
||||
onChange={handleChange}
|
||||
className="w-full px-4 py-1.5 border-2 border-dashed rounded-md text-sm focus:outline-none transition border-gray-300 focus:ring-2 focus:border-dashed focus:ring-[var(--primary)]"
|
||||
/>
|
||||
{!isTyping && (
|
||||
<RiPencilFill className="absolute top-2.5 right-2 text-[17px]" />
|
||||
)}
|
||||
</> :
|
||||
"Add Recipient and Message"
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
|
@ -354,7 +409,7 @@ const ProcessStepOne = () => {
|
|||
{/* Recipients */}
|
||||
<section className="mt-8">
|
||||
|
||||
{
|
||||
{!isTamplate &&
|
||||
transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES &&
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-lg font-medium mb-4 pt-6 border-t text-[var(--processStepOneTextHead)]">Signers</h2>
|
||||
|
|
@ -370,7 +425,7 @@ const ProcessStepOne = () => {
|
|||
}
|
||||
|
||||
{
|
||||
signType !== SignTypeEnum.selfSign &&
|
||||
(signType !== SignTypeEnum.selfSign || showSelfSignWarnModal) &&
|
||||
<div className="flex flex-col gap-4 align-start">
|
||||
<RecipientCards transactionTemplateToggle={transactionTemplateToggle} setEditRecipient={setEditRecipient} setEditCase={setEditCase} />
|
||||
{/* {signType !== "self-sign" && <RecipientAddCard />} */}
|
||||
|
|
@ -420,6 +475,7 @@ const ProcessStepOne = () => {
|
|||
</div>
|
||||
|
||||
{globalLoader.isLoading && <Loader />}
|
||||
{showSelfSignWarnModal && <SelfSignWarnModal setShowSelfSignWarnModal={setShowSelfSignWarnModal} />}
|
||||
</div >
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { FaPlus } from "react-icons/fa";
|
||||
import { addRecipient, Recipient, recipientType, TRANSACTION_TEMPLATE_TOGGLE, updateRecipient } from "../../../store/slices/selectedRecipientsSlice";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { AppDispatch } from "../../../store/store";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { AppDispatch, RootState } from "../../../store/store";
|
||||
import api from "../../../config/api";
|
||||
import ContactModal from "./ContactModal";
|
||||
import useDebouncedEffect from "../../../utlity/useDebounce";
|
||||
|
|
@ -29,6 +29,8 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
|
|||
JSON.parse(sessionStorage.getItem('activeAccountId') || 'null')
|
||||
);
|
||||
|
||||
const recipients = useSelector((state: RootState) => state.selectedRecipients.recipients);
|
||||
console.log("recipients.length in AddCase", recipients)
|
||||
|
||||
const [contactData, setContactData] = useState<ContactTypeObj>({ type: "", contacts: [] });
|
||||
const [openContactModal, setOpenContactModal] = useState(false);
|
||||
|
|
@ -47,6 +49,14 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
|
|||
const [lastQuery, setLastQuery] = useState<string | null>(null);
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [manualFocusTriggered, setManualFocusTriggered] = useState(false);
|
||||
const [errors, setErrors] = useState({
|
||||
roleName: false,
|
||||
lastName: false,
|
||||
email: false,
|
||||
emailFormat: false,
|
||||
type: false,
|
||||
});
|
||||
|
||||
|
||||
|
||||
console.log(recipient)
|
||||
|
|
@ -62,7 +72,37 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
|
|||
}, [openContactModal]);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if the recipient exists in the list (using uuid or id or email)
|
||||
const exists = recipients.some((r) => {
|
||||
return (
|
||||
(r.uuid && r.uuid === editRecipient?.uuid) ||
|
||||
(r.id && r.id === editRecipient?.id) ||
|
||||
(r.email && r.email === editRecipient?.email)
|
||||
);
|
||||
});
|
||||
console.log("Line 83", exists)
|
||||
if (editCase === "Edit" && editRecipient) {
|
||||
console.log("editRecipientAdd", editRecipient)
|
||||
if (!exists) {
|
||||
// Recipient was deleted -> reset
|
||||
setEditCase('');
|
||||
setEditRecipient(null);
|
||||
setRecipients({
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
email: "",
|
||||
hostName: "",
|
||||
hostEmail: "",
|
||||
roleName: "",
|
||||
type: recipientType.SIGNER,
|
||||
allowSenderToEdit: true,
|
||||
allowSenderToDelete: true,
|
||||
uuid: "",
|
||||
newOne: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setRecipients({
|
||||
// id:editRecipient.id,
|
||||
firstName: editRecipient.firstName || "",
|
||||
|
|
@ -78,34 +118,103 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
|
|||
newOne: editRecipient.newOne ?? false
|
||||
});
|
||||
}
|
||||
}, [editCase, editRecipient]);
|
||||
}, [editCase, editRecipient, recipients]);
|
||||
|
||||
|
||||
const handleRecipientAdd = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
console.log("recipientrecipient", recipient)
|
||||
const newErrors = {
|
||||
roleName: false,
|
||||
lastName: false,
|
||||
email: false,
|
||||
emailFormat: false,
|
||||
type: false,
|
||||
};
|
||||
|
||||
if(transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS) {
|
||||
if (!recipient.lastName || !recipient.email || !recipient.type) return;
|
||||
const roleNameFilled = recipient.roleName.trim();
|
||||
const lastNameFilled = recipient.lastName.trim();
|
||||
const emailFilled = recipient.email.trim();
|
||||
const firstNameFilled = recipient.firstName.trim()
|
||||
|
||||
// ✅ Email format validation
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(recipient.email)) {
|
||||
console.log("Invalid email format");
|
||||
|
||||
// 🔹 Check if we're in TRANSACTIONS mode
|
||||
if (transactionTemplateToggle === TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS) {
|
||||
if (lastNameFilled || emailFilled) {
|
||||
if (lastNameFilled && !emailFilled) {
|
||||
setErrors({ ...newErrors, email: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!lastNameFilled && emailFilled) {
|
||||
setErrors({ ...newErrors, lastName: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!emailRegex.test(emailFilled)) {
|
||||
setErrors({ ...newErrors, emailFormat: true });
|
||||
return;
|
||||
}
|
||||
|
||||
submitForm();
|
||||
return;
|
||||
}
|
||||
|
||||
// Neither lastName nor email is filled — treat as invalid
|
||||
setErrors({ ...newErrors, lastName: true, email: true });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// // 1. If either lastName or email is filled, validate their presence mutually
|
||||
// if ( emailFilled) {
|
||||
|
||||
// // if (lastNameFilled && !emailFilled) {
|
||||
// // setErrors({ ...newErrors, email: true });
|
||||
// // return;
|
||||
// // }
|
||||
|
||||
// // if (!lastNameFilled && emailFilled) {
|
||||
// // setErrors({ ...newErrors, lastName: true });
|
||||
// // return;
|
||||
// // }
|
||||
|
||||
// // // Both lastName and email are filled — check email format
|
||||
// if (!emailRegex.test(emailFilled)) {
|
||||
// setErrors({ ...newErrors, emailFormat: true });
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // Passed validation, submit
|
||||
// submitForm();
|
||||
// return;
|
||||
// }
|
||||
|
||||
|
||||
if (roleNameFilled || lastNameFilled || emailFilled || firstNameFilled) {
|
||||
submitForm()
|
||||
return
|
||||
}
|
||||
|
||||
// 2. If both lastName and email empty, validate roleName only
|
||||
if (!roleNameFilled) {
|
||||
setErrors({ ...newErrors, roleName: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// roleName filled, submit immediately
|
||||
submitForm();
|
||||
|
||||
function submitForm() {
|
||||
if (editCase === 'Edit') {
|
||||
dispatch(updateRecipient(recipient));
|
||||
} else {
|
||||
dispatch(addRecipient({
|
||||
...recipient,
|
||||
uuid: uuidv4(),
|
||||
newOne: true
|
||||
newOne: true,
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -120,62 +229,24 @@ const RecipientAddCard = ({ transactionTemplateToggle, editRecipient, editCase,
|
|||
allowSenderToEdit: true,
|
||||
allowSenderToDelete: true,
|
||||
uuid: "",
|
||||
newOne: false
|
||||
newOne: false,
|
||||
});
|
||||
|
||||
// setRecipients(null),
|
||||
setEditCase("")
|
||||
setManualFocusTriggered(false)
|
||||
setErrors({
|
||||
roleName: false,
|
||||
lastName: false,
|
||||
email: false,
|
||||
emailFormat: false,
|
||||
type: false,
|
||||
});
|
||||
|
||||
setEditCase("");
|
||||
setManualFocusTriggered(false);
|
||||
formRef.current?.reset();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// if (!recipient.firstName.length) return;
|
||||
|
||||
// const handler = setTimeout(() => {
|
||||
// api.get(`/api/contact?firstName=${recipient.firstName}`)
|
||||
// .then(res => {
|
||||
// setContactData({ type: "firstName", contacts: res.data.content });
|
||||
// })
|
||||
// .catch(err => console.log(err));
|
||||
// }, 500); // wait 500ms after the last change
|
||||
|
||||
// return () => {
|
||||
// clearTimeout(handler); // cancel previous timeout if effect runs again
|
||||
// };
|
||||
|
||||
// }, [recipient.firstName]);
|
||||
|
||||
// useDebouncedEffect(() => {
|
||||
// if (!recipient.firstName.length) return;
|
||||
// api.get(`/api/contact?firstName=${recipient.firstName}`)
|
||||
// .then(res => {
|
||||
// setContactData({ type: "firstName", contacts: res.data.content });
|
||||
// })
|
||||
// .catch(err => console.log(err));
|
||||
// }, [recipient.firstName], 500);
|
||||
|
||||
// useDebouncedEffect(() => {
|
||||
// if (!recipient.lastName.length) return
|
||||
// api.get(`/api/contact?lastName=${recipient.lastName}`)
|
||||
// .then(res => {
|
||||
// setContactData({ type: "lastName", contacts: res.data.content });
|
||||
// })
|
||||
// .catch(err => console.log(err))
|
||||
// }, [recipient.lastName], 500);
|
||||
|
||||
// useDebouncedEffect(() => {
|
||||
// if (!recipient.email.length) return
|
||||
// api.get(`/api/contact?email=${recipient.email}`)
|
||||
// .then(res => {
|
||||
// setContactData({ type: "email", contacts: res.data.content });
|
||||
// })
|
||||
// .catch(err => console.log(err))
|
||||
// }, [recipient.email], 500);
|
||||
|
||||
const handleContactModalBlur = () => {
|
||||
setTimeout(() => {
|
||||
setOpenContactModal(false)
|
||||
|
|
@ -284,42 +355,48 @@ useEffect(() => {
|
|||
{/* Top Row: Role Input + Settings */}
|
||||
<div className="flex justify-between items-center">
|
||||
{/* Role Name Input */}
|
||||
{/* Role Name Input */}
|
||||
<div className="w-64">
|
||||
<input
|
||||
required={recipient.firstName === "" && recipient.lastName === "" && recipient.email === ""}
|
||||
type="text"
|
||||
onFocus={() => setOpenContactModal(true)}
|
||||
onBlur={handleContactModalBlur}
|
||||
value={recipient.roleName}
|
||||
placeholder="Role name"
|
||||
className="w-64 px-3 py-2 me-3 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
|
||||
onChange={(e) => setRecipients({ ...recipient, roleName: e.target.value })}
|
||||
className={`w-full px-3 py-2 border rounded-md text-sm focus:outline-none transition
|
||||
${errors.roleName ? 'border-red-500 ring-2 ring-red-300' : 'border-gray-300 focus:ring-2 focus:ring-[var(--primary)]'}`}
|
||||
onChange={(e) => {
|
||||
setRecipients({ ...recipient, roleName: e.target.value });
|
||||
setErrors(prev => ({ ...prev, roleName: false }));
|
||||
}}
|
||||
/>
|
||||
{errors.roleName && (
|
||||
<p className="text-red-500 text-xs mt-1">Role is required.</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
{/* Settings + Permissions */}
|
||||
<div className="flex items-center">
|
||||
<div className="flex justify-between">
|
||||
{/* Permission Status Text - Always Visible when settings open */}
|
||||
<div
|
||||
className={`transition-all duration-600 ease-in-out ${showAdvanceSettings ? 'max-h-10 opacity-100 me-3' : 'max-h-0 opacity-0 '
|
||||
} overflow-hidden`}
|
||||
>
|
||||
<div className="text-sm text-gray-600">
|
||||
{recipient.allowSenderToEdit && recipient.allowSenderToDelete && "Full permissions granted"}
|
||||
<div className="flex justify-between w-full">
|
||||
{/* Permission Status Text - Always Visible */}
|
||||
<div className="max-h-10 opacity-100 me-3 overflow-hidden">
|
||||
<div className="text-sm text-gray-600 pt-0.5">
|
||||
{/* {recipient.allowSenderToEdit && recipient.allowSenderToDelete && "Full permissions granted"}
|
||||
{recipient.allowSenderToEdit && !recipient.allowSenderToDelete && "Edit permission granted"}
|
||||
{!recipient.allowSenderToEdit && recipient.allowSenderToDelete && "Delete permission granted"}
|
||||
{!recipient.allowSenderToEdit && !recipient.allowSenderToDelete && "No permissions granted"}
|
||||
{!recipient.allowSenderToEdit && !recipient.allowSenderToDelete && "No permissions granted"} */}
|
||||
Permissions granted :
|
||||
</div>
|
||||
</div>
|
||||
{/* Animated Permission Controls */}
|
||||
<div className={`overflow-visible transition-all duration-900 ease-in-out ${showAdvanceSettings ? 'max-w-80 opacity-100' : 'max-w-0 opacity-0'
|
||||
}`}>
|
||||
|
||||
{/* Permission Controls - Always Visible */}
|
||||
<div className="max-w-80 opacity-100 overflow-visible">
|
||||
<div className="flex items-center space-x-3 pr-1.5">
|
||||
{/* Edit Permission */}
|
||||
<div className="relative group">
|
||||
<label
|
||||
className={`relative flex items-center justify-center w-6 h-6 border-2 rounded-lg cursor-pointer transition-all duration-300 transform hover:scale-105 ${recipient.allowSenderToEdit
|
||||
? 'bg-blue-100 border-blue-500 shadow-lg'
|
||||
: 'bg-white border-gray-300 hover:border-blue-400'
|
||||
className={`relative flex items-center justify-center w-12 h-7 border-2 rounded-lg cursor-pointer transition-all duration-300 transform hover:scale-105 ${recipient.allowSenderToEdit
|
||||
? 'bg-blue-100 border-blue-500 shadow-md'
|
||||
: 'bg-white border-gray-300 hover:border-blue-400 text-gray-400'
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
|
|
@ -333,14 +410,14 @@ useEffect(() => {
|
|||
className="absolute w-full h-full opacity-0 cursor-pointer"
|
||||
/>
|
||||
<FiEdit
|
||||
className={`w-3 h-3 pointer-events-none transition-all duration-300 ${recipient.allowSenderToEdit ? 'text-blue-600' : 'text-gray-400'
|
||||
className={`w-2.5 h-2.5 pointer-events-none transition-all duration-300 ${recipient.allowSenderToEdit ? 'text-blue-600' : 'text-gray-400'
|
||||
}`}
|
||||
/>
|
||||
/><span className="ms-0.5 text-[12px]">Edit</span>
|
||||
</label>
|
||||
{/* Tooltip */}
|
||||
<div className="absolute bottom-0 -translate-y-full left-1/2 transform -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-20">
|
||||
<div className="bg-gray-800 text-white text-xs rounded-lg py-2 px-3 whitespace-nowrap">
|
||||
Allow Edit Permission
|
||||
Edit Permission
|
||||
<div className="absolute bottom-[-5px] left-1/2 transform -translate-x-1/2 border-4 border-transparent border-b-gray-800"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -349,9 +426,9 @@ useEffect(() => {
|
|||
{/* Delete Permission */}
|
||||
<div className="relative group">
|
||||
<label
|
||||
className={`relative flex items-center justify-center w-6 h-6 border-2 rounded-lg cursor-pointer transition-all duration-300 transform hover:scale-105 ${recipient.allowSenderToDelete
|
||||
? 'bg-red-100 border-red-500 shadow-lg'
|
||||
: 'bg-white border-gray-300 hover:border-red-400'
|
||||
className={`relative flex items-center justify-center w-15 h-7 border-2 rounded-lg cursor-pointer transition-all duration-300 transform hover:scale-105 ${recipient.allowSenderToDelete
|
||||
? 'bg-red-100 border-red-500 shadow-md'
|
||||
: 'bg-white border-gray-300 hover:border-red-400 text-gray-400'
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
|
|
@ -367,12 +444,12 @@ useEffect(() => {
|
|||
<FiTrash
|
||||
className={`w-3 h-3 pointer-events-none transition-all duration-300 ${recipient.allowSenderToDelete ? 'text-red-600' : 'text-gray-400'
|
||||
}`}
|
||||
/>
|
||||
/><span className="ms-0.5 text-[12px]">Delete</span>
|
||||
</label>
|
||||
{/* Tooltip */}
|
||||
<div className="absolute bottom-0 -translate-y-full left-1/2 transform -translate-x-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-20">
|
||||
<div className="bg-gray-800 text-white text-xs rounded-lg py-2 px-3 whitespace-nowrap">
|
||||
Allow Delete Permission
|
||||
Delete Permission
|
||||
<div className="absolute bottom-[-5px] left-1/2 transform -translate-x-1/2 border-4 border-transparent border-b-gray-800"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -380,62 +457,68 @@ useEffect(() => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Settings Icon */}
|
||||
<div className="relative group">
|
||||
<p
|
||||
className="text-blue-600 hover:text-blue-700 cursor-pointer transition-colors duration-200 p-2 rounded-full hover:bg-blue-50"
|
||||
onClick={() => setShowAdvanceSettings((prev) => !prev)}
|
||||
>
|
||||
<IoIosSettings
|
||||
className={`h-6 w-6 transition-transform duration-300 ${showAdvanceSettings ? 'rotate-90' : 'rotate-0'
|
||||
}`}
|
||||
/>
|
||||
</p>
|
||||
{/* Tooltip */}
|
||||
<div className="absolute top-0 -translate-y-full left-1/2 transform -translate-x-1/2 mb-2 opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-20">
|
||||
<div className="bg-gray-800 text-white text-xs rounded-lg py-2 px-3 whitespace-nowrap">
|
||||
Advanced Settings
|
||||
<div className="absolute bottom-[-6px] left-1/2 transform -translate-x-1/2 border-4 border-transparent border-b-gray-800"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Name Inputs */}
|
||||
<div className="grid grid-cols-12 gap-4 mt-3">
|
||||
<div className="col-span-4">
|
||||
<input
|
||||
type="text"
|
||||
onFocus={() => handleFocus("firstName")}
|
||||
onBlur={handleContactModalBlur}
|
||||
value={recipient.firstName}
|
||||
placeholder="First Name"
|
||||
className="col-span-4 w-full px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
|
||||
onChange={(e) => setRecipients({ ...recipient, firstName: e.target.value })}
|
||||
className={` w-full px-4 py-2 border rounded-md text-sm focus:outline-none transition
|
||||
${errors.firstName ? 'border-red-500 ring-2 ring-red-300' : 'border-gray-300 focus:ring-2 focus:ring-[var(--primary)]'}`}
|
||||
onChange={(e) => {
|
||||
setRecipients({ ...recipient, firstName: e.target.value });
|
||||
// setErrors((prev) => ({ ...prev, firstName: false }));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* {errors.firstName && (
|
||||
<p className="text-red-500 text-xs mt-1">First Name is required</p>
|
||||
)} */}
|
||||
<div className="col-span-4">
|
||||
<input
|
||||
required={transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES}
|
||||
type="text"
|
||||
onFocus={() => handleFocus("lastName")}
|
||||
onBlur={handleContactModalBlur}
|
||||
value={recipient.lastName}
|
||||
placeholder="Last Name"
|
||||
className="col-span-4 w-full px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
|
||||
onChange={(e) => setRecipients({ ...recipient, lastName: e.target.value })}
|
||||
className={` w-full px-4 py-2 border rounded-md text-sm focus:outline-none transition
|
||||
${errors.lastName ? 'border-red-500 ring-2 ring-red-300' : 'border-gray-300 focus:ring-2 focus:ring-[var(--primary)]'}`}
|
||||
onChange={(e) => {
|
||||
setRecipients({ ...recipient, lastName: e.target.value });
|
||||
setErrors((prev) => ({ ...prev, lastName: false }));
|
||||
}}
|
||||
/>
|
||||
{errors.lastName && (
|
||||
<p className="text-red-500 text-xs mt-1">Last Name is required</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-span-4">
|
||||
<input
|
||||
required={transactionTemplateToggle !== TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES}
|
||||
type="email"
|
||||
onFocus={() => handleFocus("email")}
|
||||
onBlur={handleContactModalBlur}
|
||||
value={recipient.email}
|
||||
placeholder="Recipient Email"
|
||||
className="col-span-4 w-full px-4 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-[var(--primary)] transition"
|
||||
onChange={(e) => setRecipients({ ...recipient, email: e.target.value })}
|
||||
className={`w-full px-4 py-2 border rounded-md text-sm focus:outline-none transition
|
||||
${errors.email ? 'border-red-500 ring-2 ring-red-300' : 'border-gray-300 focus:ring-2 focus:ring-[var(--primary)]'}`}
|
||||
onChange={(e) => {
|
||||
setRecipients({ ...recipient, email: e.target.value });
|
||||
setErrors((prev) => ({ ...prev, email: false }));
|
||||
}}
|
||||
/>
|
||||
{errors.email && (
|
||||
<p className="text-red-500 text-xs mt-1">Email is required</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {
|
|||
removeRecipientAsync,
|
||||
removeSelectedRecipient,
|
||||
// setSigningOrder,
|
||||
updateRecipientOrder, // 👈 You need to define this in your slice
|
||||
updateRecipientOrder,
|
||||
} from "../../../store/slices/selectedRecipientsSlice";
|
||||
import { AppDispatch, RootState } from "../../../store/store";
|
||||
|
||||
|
|
@ -14,6 +14,7 @@ import SigningOrderDiagram from "../../../SigningOrderDiagram ";
|
|||
import { IoCloseOutline } from "react-icons/io5";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { FiEdit } from "react-icons/fi";
|
||||
import { RiPencilFill } from "react-icons/ri";
|
||||
|
||||
const getInitials = (name: string) => {
|
||||
if (!name) return "";
|
||||
|
|
@ -30,7 +31,7 @@ type CardsData = {
|
|||
transactionTemplateToggle: string
|
||||
};
|
||||
|
||||
const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateToggle }: CardsData) => {
|
||||
const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateToggle , editRecipient ,editCase }: CardsData) => {
|
||||
const templateId = useParams<{ templateId: string }>().templateId;
|
||||
let { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
|
||||
|
||||
|
|
@ -59,7 +60,6 @@ const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateTogg
|
|||
|
||||
const handleRemoveRecipient = (recipient) => {
|
||||
if (!accountId || !transactionId) return;
|
||||
// <<<<<<< HEAD
|
||||
// dispatch(removeSelectedRecipient(recipient))
|
||||
// dispatch(removeRecipientAsync([{
|
||||
// recipientId: recipient.id,
|
||||
|
|
@ -78,7 +78,6 @@ const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateTogg
|
|||
transactionId: transactionId,
|
||||
}))
|
||||
}
|
||||
// >>>>>>> origin/main
|
||||
if (recipients.length === 2) {
|
||||
let leftRecipient = recipients.filter((obj) => {
|
||||
if ("id" in obj && "id" in recipient) {
|
||||
|
|
@ -135,15 +134,19 @@ const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateTogg
|
|||
>
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
{/* {signingOrder && ( */}
|
||||
{recipients.length > 1 ? <input
|
||||
{recipients.length > 1 ? <div className="relative">
|
||||
<input
|
||||
type="number"
|
||||
value={recipient.routingOrder || 1}
|
||||
min={1}
|
||||
onChange={(e) =>
|
||||
handleOrderChange(recipient.id || recipient.email, Number(e.target.value))
|
||||
}
|
||||
className="w-8 text-center rounded text-sm text-gray-700 border-none focus:outline-none focus:ring-[var(--primary)]"
|
||||
/> : <input
|
||||
className="w-10 h-6 text-center rounded text-sm text-gray-600 focus:outline-none focus:ring-[var(--primary)] border border-dashed border-gray-400 bg-slate-100"
|
||||
/>
|
||||
{/* <RiPencilFill className="absolute top-1.5 right-0 text-[14px] text-gray-500" /> */}
|
||||
</div>
|
||||
: <input
|
||||
type="number"
|
||||
disabled
|
||||
value={recipient.routingOrder || 1}
|
||||
|
|
@ -220,11 +223,10 @@ const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateTogg
|
|||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{signingOrderModal.open &&
|
||||
<div className="fixed top-0 left-0 inset-0 z-50 bg-black/50 backdrop-blur-sm flex justify-center items-center">
|
||||
<div className="flex-col relative justify-center items-center max-w-[80%] max-h-[60vh] h-full overflow-auto">
|
||||
<div className="pb-2 text-white flex justify-between items-center">
|
||||
<div className="p-2 rounded-t-lg text-white flex justify-between items-center bg-[var(--primary)]">
|
||||
<p className="font-semibold">Signing Order</p>
|
||||
<div
|
||||
onClick={() => setSigningOrderModal({ ...signingOrderModal, open: false })}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
type PropsType = {
|
||||
setShowSelfSignWarnModal: (val: boolean) => void;
|
||||
onConfirm: () => void;
|
||||
};
|
||||
|
||||
const SelfSignWarnModal = ({ setShowSelfSignWarnModal }: PropsType) => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 backdrop-blur-sm flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-lg shadow-xl p-6 w-100">
|
||||
<h3 className="text-lg font-medium text-gray-800 mb-4">Confirm Self Sign</h3>
|
||||
<p className="text-gray-600 mb-6">
|
||||
Are you sure you want to proceed with self sign? This will remove all the recipient that you added.
|
||||
</p>
|
||||
<div className="flex justify-end gap-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowSelfSignWarnModal(false);
|
||||
navigate("./../");
|
||||
}}
|
||||
|
||||
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-md transition-colors"
|
||||
>
|
||||
No
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
// onConfirm();
|
||||
setShowSelfSignWarnModal(false);
|
||||
}}
|
||||
className="px-4 py-2 bg-red-600 text-white hover:bg-red-700 rounded-md transition-colors"
|
||||
>
|
||||
Yes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelfSignWarnModal;
|
||||
|
|
@ -78,7 +78,7 @@ const Templates = () => {
|
|||
let res = await api.post(`/api/accounts/${activeAccountId}/transactions`, payload)
|
||||
console.log("handleUseClick", res.data)
|
||||
// /process-step-one/:accountId/:transactionId
|
||||
navigate(`/process-step-one/${activeAccountId}/${res?.data?.id}`);
|
||||
navigate(`/process-step-one/${activeAccountId}/${res?.data?.id}?useTemplate=true`);
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,6 +100,18 @@ canvas {
|
|||
}
|
||||
}
|
||||
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
/* Group Border - START */
|
||||
.group-border {
|
||||
position: absolute;
|
||||
|
|
|
|||
|
|
@ -437,6 +437,8 @@ export const removeRecipientAsync = createAsyncThunk<
|
|||
|
||||
const basePath = `/api/accounts/${accountId}/${toggle}/${transactionId}/recipients`;
|
||||
|
||||
console.log(`Line 440 : ${basePath}/${recipientId}`)
|
||||
// return
|
||||
if (recipientId) {
|
||||
await api.delete(`${basePath}/${recipientId}`);
|
||||
return recipientId;
|
||||
|
|
@ -533,6 +535,9 @@ const selectedRecipientsSlice = createSlice({
|
|||
removeSelectedRecipient: (state, action: PayloadAction<Recipient>) => {
|
||||
state.recipients = state.recipients.filter((recipient) => recipient.uuid !== action.payload.uuid);
|
||||
},
|
||||
removeAllRecipients: (state) => {
|
||||
state.recipients = [];
|
||||
},
|
||||
addColorsToRecipient: (state) => {
|
||||
for (const [index, recipient] of state.recipients.entries()) {
|
||||
recipient.color = colors[index];
|
||||
|
|
@ -639,6 +644,7 @@ export const {
|
|||
setSelectedRecipients,
|
||||
toggleToTemplates,
|
||||
removeSelectedRecipient,
|
||||
removeAllRecipients,
|
||||
setSigningOrder,
|
||||
addColorsToRecipient,
|
||||
updateRecipientIds,
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ const JoyrideController = () => {
|
|||
),
|
||||
placement: "left",
|
||||
disableBeacon: true,
|
||||
|
||||
},
|
||||
{
|
||||
target: "#start-now-upload-con",
|
||||
|
|
@ -63,7 +62,6 @@ const JoyrideController = () => {
|
|||
),
|
||||
placement: "top",
|
||||
disableBeacon: true,
|
||||
|
||||
},
|
||||
];
|
||||
} else if (location.pathname.includes("/process-step-one")) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue