SME-Web/src/components/contacts/Contacts.tsx

204 lines
8.9 KiB
TypeScript

import { useEffect, useState, useRef } from 'react';
import { FaEllipsisV } from 'react-icons/fa';
import api from '../../config/api';
import ContactFormModal, { ContactData } from './ContactsFormModal';
import { Bounce, toast } from 'react-toastify';
import { IoMdAdd } from 'react-icons/io';
import LoaderUi from '../../helper/LoaderUi';
type ContactSchema = [{
id: number,
firstName: string
lastName: string
email: string
}]
const Contacts = () => {
const activeAccountId = JSON.parse(sessionStorage.getItem('activeAccountId') || 'null');
const [contacts, setContacts] = useState<ContactSchema>([]);
const [loading, setLoading] = useState(true);
const [openMenuId, setOpenMenuId] = useState(null);
const blurTimeoutRef = useRef(null);
const [createAndInviteLoader, setCreateAndInviteLoader] = useState(false);
const [showModal, setShowModal] = useState(false)
const [editJsonData, setEditJsonData] = useState({})
const [actionStatus, setActionStatus] = useState("")
console.log("editJsonData", editJsonData)
const fetchContacts = async () => {
setLoading(true)
try {
const res = await api.get(`/api/accounts/${activeAccountId}/contacts`);
setContacts(res.data.content || []);
} catch (error) {
console.error('Failed to fetch contacts:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
if (activeAccountId) {
fetchContacts();
}
}, [activeAccountId]);
const handleEdit = (contactId) => {
console.log('Edit contact:', contactId);
let jsonEditData = contacts.find((prev) => prev.id === contactId)
setEditJsonData(jsonEditData)
setActionStatus("Edit")
setShowModal((prev) => !prev)
setOpenMenuId(null);
};
const handleDelete = async (contactId) => {
console.log('Delete contact:', contactId);
try {
let res = await api.delete(`/api/accounts/${activeAccountId}/contacts/${contactId}`)
if (res?.data) {
toast.error("Contact delete successfully!", {
position: "top-center",
autoClose: 1500,
transition: Bounce,
});
fetchContacts()
setOpenMenuId(null);
}
} catch (error) {
console.log(error)
}
};
const toggleMenu = (contactId) => {
// Cancel any pending blur timeout (prevents blinking)
clearTimeout(blurTimeoutRef.current);
setOpenMenuId((prevId) => (prevId === contactId ? null : contactId));
};
const handleBlur = () => {
blurTimeoutRef.current = setTimeout(() => {
setOpenMenuId(null);
}, 100);
};
const handleContactSubmit = async (data: ContactData, type: "") => {
try {
let res;
setCreateAndInviteLoader(true);
if (type === 'Edit') {
res = await api.put(`/api/accounts/${activeAccountId}/contacts`, [data])
} else {
res = await api.post(`/api/accounts/${activeAccountId}/contacts`, [data]);
}
if (res.status === 200) {
toast.success('Contact created successfully!', {
position: "top-center",
autoClose: 1500,
transition: Bounce,
});
setShowModal(false)
fetchContacts();
}
// return res;
} catch (err) {
console.error("Error creating contact", err);
toast.error("Failed to create contact", {
position: "top-center",
autoClose: 1500,
transition: Bounce,
});
throw err;
} finally {
setCreateAndInviteLoader(false);
}
};
return (
<>
<div className='px-1.5'>
<div className='flex justify-end my-2 cursor-pointer'>
<div
onClick={() => setShowModal((prev) => !prev)}
className="flex items-center gap-2 bg-[var(--primary)] px-3 py-1.5 rounded-sm hover:opacity-90 ">
<IoMdAdd className="text-white" />
<button
className="text-white rounded text-sm hover:opacity-90 cursor-pointer">
Add Contact
</button>
</div>
</div>
<div className="overflow-y-auto max-h-[79vh] border border-gray-200 rounded shadow-sm no-scrollbar">
<table className="min-w-full text-sm bg-white">
<thead className="bg-gray-100 text-gray-700 uppercase text-xs tracking-wider sticky top-0 z-10">
<tr>
<th className="px-4 py-3 text-left">First Name</th>
<th className="px-4 py-3 text-left">Last Name</th>
<th className="px-4 py-3 text-left">Email</th>
<th className="px-4 py-3 text-center">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{loading ? (
<tr>
<td colSpan="4" className="relative h-100"><LoaderUi/></td>
</tr>
) : contacts.length === 0 ? (
<tr>
<td colSpan="4" className="text-center py-4">No contacts found.</td>
</tr>
) : (
contacts.map((contact) => (
<tr key={contact.id} className="hover:bg-gray-50">
<td className="px-4 py-3">{contact.firstName}</td>
<td className="px-4 py-3">{contact.lastName}</td>
<td className="px-4 py-3">{contact.email}</td>
<td className="px-4 py-3 text-center">
<div
className="relative inline-block"
tabIndex={0}
onBlur={handleBlur}
>
<button
className="text-gray-600 hover:text-black p-1 focus:outline-none"
onClick={() => toggleMenu(contact.id)}
>
<FaEllipsisV />
</button>
{openMenuId === contact.id && (
<div className="absolute top-full right-0 mt-1 bg-white border border-gray-200 rounded shadow-lg z-20 min-w-[100px]">
<button
onMouseDown={() => handleEdit(contact.id)}
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-blue-600"
>
Edit
</button>
<button
onMouseDown={() => handleDelete(contact.id)}
className="block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-red-600"
>
Delete
</button>
</div>
)}
</div>
</td>
</tr>
))
)}
</tbody>
</table>
{showModal &&
<ContactFormModal onSubmit={handleContactSubmit}
loading={createAndInviteLoader} setShowModal={setShowModal} actionStatus={actionStatus} editJsonData={editJsonData} />
}
</div>
</div>
</>
);
};
export default Contacts;