added restore feature for contacts

This commit is contained in:
Pramod Mahajan 2025-05-29 19:58:45 +05:30
parent f9d94d1b18
commit 114aac089b
4 changed files with 266 additions and 151 deletions

View File

@ -0,0 +1,21 @@
import React, { createContext, useContext, useState } from "react";
const DireContext = createContext(undefined);
export const DireProvider = ({ children }) => {
const [dirActions, setDirActions] = useState([]);
return (
<DireContext.Provider value={{ dirActions, setDirActions }}>
{children}
</DireContext.Provider>
);
};
export const useDir = () => {
const context = useContext(DireContext);
if (!context) {
throw new Error("useDir must be used within a <DireProvider>");
}
return context;
};

View File

@ -3,6 +3,7 @@ import Avatar from "../common/Avatar";
import { getBucketNameById } from "./DirectoryUtils";
import { useBuckets } from "../../hooks/useDirectory";
import { getPhoneIcon } from "./DirectoryUtils";
import { useDir } from "../../Context/DireContext";
const CardViewDirectory = ({
IsActive,
contact,
@ -11,8 +12,10 @@ const CardViewDirectory = ({
setOpen_contact,
setIsOpenModalNote,
IsDeleted,
restore,
}) => {
const { buckets } = useBuckets();
const { dirActions, setDirActions } = useDir();
return (
<div
className="card text-start border-1"
@ -30,54 +33,70 @@ const CardViewDirectory = ({
(contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""
}
/>{" "}
<span className="text-heading fs-6"> {contact.name}</span>
<span className="text-heading fs-6"> {contact.name}</span>
</div>
<div>
<div className={`dropdown z-2 ${!IsActive && "d-none"}`}>
<button
type="button"
className="btn btn-xs btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0 m-0"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i
className="bx bx-dots-vertical-rounded bx-sm text-muted p-0"
data-bs-toggle="tooltip"
data-bs-offset="0,8"
data-bs-placement="top"
data-bs-custom-class="tooltip-dark"
title="More Action"
></i>
</button>
<ul className="dropdown-menu dropdown-menu-end w-auto">
<li
onClick={() => {
setSelectedContact(contact);
setIsOpenModal(true);
}}
{IsActive && (
<div className="dropdown z-2">
<button
type="button"
className="btn btn-xs btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0 m-0"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<a className="dropdown-item px-2 cursor-pointer py-1">
<i className="bx bx-edit bx-xs text-primary me-2"></i>
<span className="align-left ">Modify</span>
</a>
</li>
<li>
<a
className="dropdown-item px-2 cursor-pointer py-1"
onClick={() => IsDeleted(contact.id)}
<i
className="bx bx-dots-vertical-rounded bx-sm text-muted p-0"
data-bs-toggle="tooltip"
data-bs-offset="0,8"
data-bs-placement="top"
data-bs-custom-class="tooltip-dark"
title="More Action"
></i>
</button>
<ul className="dropdown-menu dropdown-menu-end w-auto">
<li
onClick={() => {
setSelectedContact(contact);
setIsOpenModal(true);
}}
>
<i className="bx bx-trash text-danger bx-xs me-2"></i>
<span className="align-left">Delete</span>
</a>
</li>
</ul>
</div>
<a className="dropdown-item px-2 cursor-pointer py-1">
<i className="bx bx-edit bx-xs text-primary me-2"></i>
<span className="align-left ">Modify</span>
</a>
</li>
<li>
<a
className="dropdown-item px-2 cursor-pointer py-1"
onClick={() => IsDeleted(contact.id)}
>
<i className="bx bx-trash text-danger bx-xs me-2"></i>
<span className="align-left">Delete</span>
</a>
</li>
</ul>
</div>
)}
{!IsActive && (
<i
className={`bx bx-history ${
dirActions.action && dirActions.id === contact.id
? "bx-spin"
: ""
} me-1 text-primary cursor-pointer`}
title="Restore"
onClick={() => {
setDirActions({ action: false, id: contact.id });
restore(contact.id);
}}
></i>
)}
</div>
</div>
<ul className="list-inline m-0 ps-4">
<li className="list-inline-item me-1" style={{ fontSize: "10px" }}>
<i className="bx bx-building bx-xs"></i>
<i className="fa-solid fa-briefcase me-2"></i>
</li>
<li className="list-inline-item text-small">
{contact.organization}
@ -97,7 +116,7 @@ const CardViewDirectory = ({
{contact.contactEmails[0] && (
<ul className="list-inline my-1 ">
<li className="list-inline-item me-2">
<i className="bx bx-envelope bx-xs"></i>
<i className="bx bx-envelope bx-xs"></i>
</li>
<li className="list-inline-item text-small">
{contact.contactEmails[0]?.emailAddress}
@ -122,27 +141,28 @@ const CardViewDirectory = ({
<ul className="list-inline m-0">
<li className="list-inline-item me-2 my-1">
<i className="fa-solid fa-tag fs-6"></i>
<i className="fa-solid fa-tag fs-6"></i>
</li>
<li className="list-inline-item text-small active">
{contact.contactCategory.name}
</li>
</ul>
<ul className="list-inline m-0">
{contact.bucketIds?.map((bucketId) => (
<li key={bucketId} className="list-inline-item me-1">
<span className="badge bg-label-primary rounded-pill d-flex align-items-center gap-1" style={{padding:'0.1rem 0.3rem'}}>
<i className="bx bx-pin bx-xs"></i>
<span className="small-text">
{getBucketNameById(buckets, bucketId)}
</span>
</span>
</li>
))}
</ul>
<ul className="list-inline m-0">
{contact.bucketIds?.map((bucketId) => (
<li key={bucketId} className="list-inline-item me-1">
<span
className="badge bg-label-primary rounded-pill d-flex align-items-center gap-1"
style={{ padding: "0.1rem 0.3rem" }}
>
<i className="bx bx-pin bx-xs"></i>
<span className="small-text">
{getBucketNameById(buckets, bucketId)}
</span>
</span>
</li>
))}
</ul>
</div>
</div>
);

View File

@ -1,74 +1,128 @@
import React from 'react'
import Avatar from '../common/Avatar';
import { getEmailIcon,getPhoneIcon } from './DirectoryUtils';
import React, { useEffect } from "react";
import Avatar from "../common/Avatar";
import { getEmailIcon, getPhoneIcon } from "./DirectoryUtils";
import { useDir } from "../../Context/DireContext";
const ListViewDirectory = ({
IsActive,
contact,
setSelectedContact,
setIsOpenModal,
setOpen_contact,
setIsOpenModalNote,
IsDeleted,
restore,
}) => {
const { dirActions, setDirActions } = useDir();
const ListViewDirectory = ({IsActive, contact,setSelectedContact,setIsOpenModal,setOpen_contact,setIsOpenModalNote,IsDeleted}) => {
return (
<tr className={!IsActive ? "bg-light" : ""}>
<td className="text-start cursor-pointer" style={{ width: "18%" }} colSpan={2} onClick={() => {
if (IsActive) {
setIsOpenModalNote(true);
setOpen_contact(contact);
}
}}>
<div className="d-flex align-items-center">
<Avatar
size="xs"
classAvatar="m-0"
firstName={(contact?.name || "").trim().split(" ")[0]?.charAt(0) || ""}
lastName={(contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""}
/>
<span className="text-truncate mx-0" style={{ maxWidth: "150px" }}>{contact?.name || ""}</span>
</div>
</td>
<td className="px-2" style={{ width: "20%" }}>
<div className="d-flex flex-column align-items-start text-truncate">
{contact.contactEmails?.map((email, index) => (
<span key={email.id} className="text-truncate">
<i className={getEmailIcon(email.label)} style={{ fontSize: "12px" }}></i>
<a href={`mailto:${email.emailAddress}`} className="text-decoration-none ms-1">{email.emailAddress}</a>
</span>
))}
</div>
</td>
<td className="px-2" style={{ width: "20%" }}>
<div className="d-flex flex-column align-items-start text-truncate">
{contact.contactPhones?.map((phone, index) => (
<span key={phone.id}>
<i className={getPhoneIcon(phone.label)} style={{ fontSize: "12px" }}></i>
<span className="ms-1">{phone.phoneNumber}</span>
</span>
))}
</div>
</td>
<td colSpan={2} className="text-start text-truncate px-2" style={{ width: "20%", maxWidth: "200px" }}>
{contact.organization}
</td>
<td className="px-2" style={{ width: "10%" }}>
<span className="badge badge-outline-secondary">
{contact?.contactCategory?.name}
</span>
</td>
{IsActive && (
<td className="align-middle text-center" style={{ width: "12%" }}>
<i className="bx bx-edit bx-sm text-primary cursor-pointer me-2"
<tr className={!IsActive ? "bg-light" : ""}>
<td
className="text-start cursor-pointer"
style={{ width: "18%" }}
colSpan={2}
onClick={() => {
setSelectedContact(contact);
setIsOpenModal(true);
}}></i>
<i className="bx bx-trash bx-sm text-danger cursor-pointer"
onClick={() => IsDeleted(contact.id)}></i>
</td>
)}
</tr>
if (IsActive) {
setIsOpenModalNote(true);
setOpen_contact(contact);
}
}}
>
<div className="d-flex align-items-center">
<Avatar
size="xs"
classAvatar="m-0"
firstName={
(contact?.name || "").trim().split(" ")[0]?.charAt(0) || ""
}
lastName={
(contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""
}
/>
<span className="text-truncate mx-0" style={{ maxWidth: "150px" }}>
{contact?.name || ""}
</span>
</div>
</td>
<td className="px-2" style={{ width: "20%" }}>
<div className="d-flex flex-column align-items-start text-truncate">
{contact.contactEmails?.map((email, index) => (
<span key={email.id} className="text-truncate">
<i
className={getEmailIcon(email.label)}
style={{ fontSize: "12px" }}
></i>
<a
href={`mailto:${email.emailAddress}`}
className="text-decoration-none ms-1"
>
{email.emailAddress}
</a>
</span>
))}
</div>
</td>
<td className="px-2" style={{ width: "20%" }}>
<div className="d-flex flex-column align-items-start text-truncate">
{contact.contactPhones?.map((phone, index) => (
<span key={phone.id}>
<i
className={getPhoneIcon(phone.label)}
style={{ fontSize: "12px" }}
></i>
<span className="ms-1">{phone.phoneNumber}</span>
</span>
))}
</div>
</td>
<td
colSpan={2}
className="text-start text-truncate px-2"
style={{ width: "20%", maxWidth: "200px" }}
>
{contact.organization}
</td>
<td className="px-2" style={{ width: "10%" }}>
<span className="badge badge-outline-secondary">
{contact?.contactCategory?.name}
</span>
</td>
<td className="align-middle text-center" style={{ width: "12%" }}>
{IsActive && (
<>
<i
className="bx bx-edit bx-sm text-primary cursor-pointer me-2"
onClick={() => {
setSelectedContact(contact);
setIsOpenModal(true);
}}
></i>
<i
className="bx bx-trash bx-sm text-danger cursor-pointer"
onClick={() => IsDeleted(contact.id)}
></i>
</>
)}
{!IsActive && (
<i
className={`bx bx-history ${
dirActions.action && dirActions.id === contact.id ? "bx-spin" : ""
} me-1 text-primary cursor-pointer`}
title="Restore"
onClick={() => {
setDirActions({ action: false, id: contact.id });
restore(contact.id);
}}
></i>
)}
</td>
</tr>
);
};
export default ListViewDirectory;
export default ListViewDirectory;

View File

@ -19,6 +19,7 @@ import DirectoryListTableHeader from "./DirectoryListTableHeader";
import DirectoryPageHeader from "./DirectoryPageHeader";
import ManageBucket from "../../components/Directory/ManageBucket";
import {useFab} from "../../Context/FabContext";
import {DireProvider, useDir} from "../../Context/DireContext";
const Directory = () =>
{
@ -33,12 +34,15 @@ const Directory = () =>
const [listView, setListView] = useState(false);
const [selectedBucketIds, setSelectedBucketIds] = useState([]);
const [deleteContact, setDeleteContact] = useState(null);
const [ IsDeleting, setIsDeletng ] = useState( false );
const [openBucketModal,setOpenBucketModal] = useState(false)
const [ IsDeleting, setDeleting ] = useState( false );
const [ openBucketModal, setOpenBucketModal ] = useState( false )
const [tempSelectedBucketIds, setTempSelectedBucketIds] = useState([]);
const [ tempSelectedCategoryIds, setTempSelectedCategoryIds ] = useState( [] );
const {setActions} = useFab()
const {setActions} = useFab()
const { dirActions, setDirActions } = useDir();
const { contacts, loading , refetch} = useDirectory(IsActive);
const { contactCategory, loading: contactCategoryLoading } =
@ -78,28 +82,43 @@ const Directory = () =>
}
};
const handleDeleteContact = async () => {
try {
setIsDeletng(true);
const contacts_cache = getCachedData("contacts")?.data || [];
const response = await DirectoryRepository.DeleteContact(deleteContact);
const updatedContacts = ContactList.filter( ( c ) => c.id !== deleteContact );
setContactList(updatedContacts);
cacheData("Contacts", {data:updatedContacts,isActive:IsActive});
showToast("Contact deleted successfully", "success");
setDeleteContact(null);
setIsDeletng(false);
} catch (error) {
const msg =
error.response.data.message ||
error.message ||
"Error occured during API calling";
showToast(msg, "error");
setIsDeletng(false);
const handleDeleteContact = async (overrideId = null) => {
try
{
if (!IsActive) {
setDirActions((prev) => ({ ...prev, action: true }));
} else {
setDeleting(true);
}
};
const id = overrideId || (!IsActive ? dirActions.id : deleteContact);
if (!id) {
showToast("No contact selected for deletion", "error");
return;
}
await DirectoryRepository.DeleteContact(id, !IsActive);
const updatedContacts = ContactList.filter((c) => c.id !== id);
setContactList(updatedContacts);
cacheData("Contacts", { data: updatedContacts, isActive: IsActive });
showToast(`Contact ${IsActive ? "Deleted":"Restored"} successfully`, "success");
setDeleteContact(null);
setDirActions({ action: false, id: null });
setDeleting(false);
} catch (error) {
const msg =
error?.response?.data?.message ||
error.message ||
"Error occurred during API call";
showToast(msg, "error");
setDeleting(false);
setDirActions({ action: false, id: null });
}
};
const closedModel = () => {
setIsOpenModal(false);
@ -306,7 +325,6 @@ const Directory = () =>
applyFilter={applyFilter}
loading={loading}
IsActive={IsActive}
setIsOpenModal={setIsOpenModal}
setOpenBucketModal={setOpenBucketModal}
/>
{!listView && loading && <p>Loading...</p>}
@ -314,7 +332,7 @@ const Directory = () =>
<p>No Matching Contact Found</p>
)}
{listView ? (
<DirectoryListTableHeader IsActive={IsActive}>
<DirectoryListTableHeader>
{loading && (
<tr>
<td colSpan={10}>Loading...</td>
@ -336,6 +354,7 @@ const Directory = () =>
setOpen_contact={setOpen_contact}
setIsOpenModalNote={setIsOpenModalNote}
IsDeleted={setDeleteContact}
restore={handleDeleteContact}
/>
))}
</DirectoryListTableHeader>
@ -354,15 +373,16 @@ const Directory = () =>
setOpen_contact={setOpen_contact}
setIsOpenModalNote={setIsOpenModalNote}
IsDeleted={setDeleteContact}
restore={handleDeleteContact}
/>
</div>
))}
</div>
)}
<div>
</div>
{!loading && currentItems < ITEMS_PER_PAGE && (
{!loading && currentItems < ITEMS_PER_PAGE && (
<nav aria-label="Page ">
<ul className="pagination pagination-sm justify-content-end py-1">
<li
@ -406,7 +426,7 @@ const Directory = () =>
</nav>
)}
</div>
</div>
</div>
);
};