pramod_Task-#399 : Added Access Permission For 'Directory User' Role #160

Merged
pramod.mahajan merged 19 commits from pramod_Task-#399 into Feature_Directory 2025-05-29 17:49:56 +00:00
4 changed files with 266 additions and 151 deletions
Showing only changes of commit 114aac089b - Show all commits

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 { getBucketNameById } from "./DirectoryUtils";
import { useBuckets } from "../../hooks/useDirectory"; import { useBuckets } from "../../hooks/useDirectory";
import { getPhoneIcon } from "./DirectoryUtils"; import { getPhoneIcon } from "./DirectoryUtils";
import { useDir } from "../../Context/DireContext";
const CardViewDirectory = ({ const CardViewDirectory = ({
IsActive, IsActive,
contact, contact,
@ -11,8 +12,10 @@ const CardViewDirectory = ({
setOpen_contact, setOpen_contact,
setIsOpenModalNote, setIsOpenModalNote,
IsDeleted, IsDeleted,
restore,
}) => { }) => {
const { buckets } = useBuckets(); const { buckets } = useBuckets();
const { dirActions, setDirActions } = useDir();
return ( return (
<div <div
className="card text-start border-1" className="card text-start border-1"
@ -33,7 +36,8 @@ const CardViewDirectory = ({
<span className="text-heading fs-6"> {contact.name}</span> <span className="text-heading fs-6"> {contact.name}</span>
</div> </div>
<div> <div>
<div className={`dropdown z-2 ${!IsActive && "d-none"}`}> {IsActive && (
<div className="dropdown z-2">
<button <button
type="button" type="button"
className="btn btn-xs btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0 m-0" className="btn btn-xs btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0 m-0"
@ -72,12 +76,27 @@ const CardViewDirectory = ({
</li> </li>
</ul> </ul>
</div> </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>
</div> </div>
<ul className="list-inline m-0 ps-4"> <ul className="list-inline m-0 ps-4">
<li className="list-inline-item me-1" style={{ fontSize: "10px" }}> <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>
<li className="list-inline-item text-small"> <li className="list-inline-item text-small">
{contact.organization} {contact.organization}
@ -123,7 +142,6 @@ const CardViewDirectory = ({
<ul className="list-inline m-0"> <ul className="list-inline m-0">
<li className="list-inline-item me-2 my-1"> <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>
<li className="list-inline-item text-small active"> <li className="list-inline-item text-small active">
{contact.contactCategory.name} {contact.contactCategory.name}
@ -133,7 +151,10 @@ const CardViewDirectory = ({
<ul className="list-inline m-0"> <ul className="list-inline m-0">
{contact.bucketIds?.map((bucketId) => ( {contact.bucketIds?.map((bucketId) => (
<li key={bucketId} className="list-inline-item me-1"> <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'}}> <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> <i className="bx bx-pin bx-xs"></i>
<span className="small-text"> <span className="small-text">
{getBucketNameById(buckets, bucketId)} {getBucketNameById(buckets, bucketId)}
@ -142,7 +163,6 @@ const CardViewDirectory = ({
</li> </li>
))} ))}
</ul> </ul>
</div> </div>
</div> </div>
); );

View File

@ -1,25 +1,47 @@
import React from 'react' import React, { useEffect } from "react";
import Avatar from '../common/Avatar'; import Avatar from "../common/Avatar";
import { getEmailIcon,getPhoneIcon } from './DirectoryUtils'; 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 ( return (
<tr className={!IsActive ? "bg-light" : ""}> <tr className={!IsActive ? "bg-light" : ""}>
<td className="text-start cursor-pointer" style={{ width: "18%" }} colSpan={2} onClick={() => { <td
className="text-start cursor-pointer"
style={{ width: "18%" }}
colSpan={2}
onClick={() => {
if (IsActive) { if (IsActive) {
setIsOpenModalNote(true); setIsOpenModalNote(true);
setOpen_contact(contact); setOpen_contact(contact);
} }
}}> }}
>
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<Avatar <Avatar
size="xs" size="xs"
classAvatar="m-0" classAvatar="m-0"
firstName={(contact?.name || "").trim().split(" ")[0]?.charAt(0) || ""} firstName={
lastName={(contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""} (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> <span className="text-truncate mx-0" style={{ maxWidth: "150px" }}>
{contact?.name || ""}
</span>
</div> </div>
</td> </td>
@ -27,8 +49,16 @@ const ListViewDirectory = ({IsActive, contact,setSelectedContact,setIsOpenModal,
<div className="d-flex flex-column align-items-start text-truncate"> <div className="d-flex flex-column align-items-start text-truncate">
{contact.contactEmails?.map((email, index) => ( {contact.contactEmails?.map((email, index) => (
<span key={email.id} className="text-truncate"> <span key={email.id} className="text-truncate">
<i className={getEmailIcon(email.label)} style={{ fontSize: "12px" }}></i> <i
<a href={`mailto:${email.emailAddress}`} className="text-decoration-none ms-1">{email.emailAddress}</a> className={getEmailIcon(email.label)}
style={{ fontSize: "12px" }}
></i>
<a
href={`mailto:${email.emailAddress}`}
className="text-decoration-none ms-1"
>
{email.emailAddress}
</a>
</span> </span>
))} ))}
</div> </div>
@ -38,14 +68,21 @@ const ListViewDirectory = ({IsActive, contact,setSelectedContact,setIsOpenModal,
<div className="d-flex flex-column align-items-start text-truncate"> <div className="d-flex flex-column align-items-start text-truncate">
{contact.contactPhones?.map((phone, index) => ( {contact.contactPhones?.map((phone, index) => (
<span key={phone.id}> <span key={phone.id}>
<i className={getPhoneIcon(phone.label)} style={{ fontSize: "12px" }}></i> <i
className={getPhoneIcon(phone.label)}
style={{ fontSize: "12px" }}
></i>
<span className="ms-1">{phone.phoneNumber}</span> <span className="ms-1">{phone.phoneNumber}</span>
</span> </span>
))} ))}
</div> </div>
</td> </td>
<td colSpan={2} className="text-start text-truncate px-2" style={{ width: "20%", maxWidth: "200px" }}> <td
colSpan={2}
className="text-start text-truncate px-2"
style={{ width: "20%", maxWidth: "200px" }}
>
{contact.organization} {contact.organization}
</td> </td>
@ -55,19 +92,36 @@ const ListViewDirectory = ({IsActive, contact,setSelectedContact,setIsOpenModal,
</span> </span>
</td> </td>
{IsActive && (
<td className="align-middle text-center" style={{ width: "12%" }}> <td className="align-middle text-center" style={{ width: "12%" }}>
<i className="bx bx-edit bx-sm text-primary cursor-pointer me-2" {IsActive && (
<>
<i
className="bx bx-edit bx-sm text-primary cursor-pointer me-2"
onClick={() => { onClick={() => {
setSelectedContact(contact); setSelectedContact(contact);
setIsOpenModal(true); setIsOpenModal(true);
}}></i> }}
<i className="bx bx-trash bx-sm text-danger cursor-pointer" ></i>
onClick={() => IsDeleted(contact.id)}></i> <i
</td> 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> </tr>
); );
}; };

View File

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