Compare commits

...

19 Commits

Author SHA1 Message Date
Pramod Mahajan
6fa0107792 Merge branch 'Feature_Directory' of https://git.marcoaiot.com/admin/marco.pms.web into pramod_Task-#398 2025-05-29 20:12:08 +05:30
Pramod Mahajan
c00a0ecff1 removed unused props 2025-05-29 20:11:26 +05:30
Pramod Mahajan
7e0acf00bd display contact profile data 2025-05-29 20:10:15 +05:30
Pramod Mahajan
bc9cbd61f3 Added id check before calling fetchContactNotes function 2025-05-29 20:09:35 +05:30
Pramod Mahajan
25bd5550c6 Add restore feature for deleted notes 2025-05-29 20:08:31 +05:30
Pramod Mahajan
069d12ecd6 aded validation for tags and rename label to bucket 2025-05-29 20:05:43 +05:30
Pramod Mahajan
054c129ba5 removed new contact button form header 2025-05-29 20:01:42 +05:30
Pramod Mahajan
3ed2048e45 changed table column email to role 2025-05-29 19:59:24 +05:30
Pramod Mahajan
114aac089b added restore feature for contacts 2025-05-29 19:58:45 +05:30
Pramod Mahajan
f9d94d1b18 fixed console warning 2025-05-29 19:57:24 +05:30
Pramod Mahajan
0069b48ca8 added wrapped DirProver for context 2025-05-29 19:56:18 +05:30
Pramod Mahajan
2b65f92f54 Support tag creation on spacebar key 2025-05-29 19:55:36 +05:30
Pramod Mahajan
5a59e79160 changed useDirectory hook calling format 2025-05-29 17:26:07 +05:30
Pramod Mahajan
b9f24e9954 changed delete contact Url 2025-05-29 17:24:58 +05:30
Pramod Mahajan
a2e34b91dc changed navigation tab layout 2025-05-29 17:21:07 +05:30
Pramod Mahajan
e94e41a952 changed font size 2025-05-29 17:20:27 +05:30
Pramod Mahajan
766f5d31bf corrected state name, mismatched 2025-05-29 10:12:56 +05:30
Pramod Mahajan
ecebef71c1 added margin btwn bucket name in filter dropdwon 2025-05-29 10:12:14 +05:30
Pramod Mahajan
93977ba5b6 Fixed case mismatch in error rendering (BucketIds ➝ bucketIds) 2025-05-29 10:10:47 +05:30
21 changed files with 711 additions and 484 deletions

View File

@ -1,3 +1,4 @@
import { DireProvider } from "./Context/DireContext";
import AppRoutes from "./router/AppRoutes";
import { ToastContainer } from "react-toastify";
@ -5,8 +6,13 @@ import { ToastContainer } from "react-toastify";
const App = () => {
return (
<div className="app">
<AppRoutes />
<ToastContainer></ToastContainer>
<DireProvider>
<AppRoutes />
</DireProvider>
<ToastContainer>
</ToastContainer>
</div>
);

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

@ -6,7 +6,7 @@ export const ContactSchema = z
contactCategoryId: z.string().nullable().optional(),
address: z.string().optional(),
description: z.string().min(1, { message: "Description is required" }),
projectIds: z.array(z.string()), // min(1, "Project is required")
projectIds: z.array(z.string()).nullable().optional(), // min(1, "Project is required")
contactEmails: z
.array(
z.object({
@ -39,7 +39,7 @@ export const ContactSchema = z
})
)
.min(1, { message: "At least one tag is required" }),
bucketIds: z.array(z.string()).min(1,{message:"At least one Label required"}),
bucketIds: z.array(z.string()).nonempty({ message: "At least one label is required" })
})
// .refine((data) => {

View File

@ -96,7 +96,7 @@ const EmployeeList = ({ employees, onChange, bucket }) => {
>
<span className="ps-2">Name {getSortIcon()}</span>
</th>
<th className="text-start">Email</th>
<th className="text-start">Role</th>
</tr>
</thead>
@ -143,7 +143,7 @@ const EmployeeList = ({ employees, onChange, bucket }) => {
</div>
</td>
<td className="text-start">
<small className="text-muted">{employee.email}</small>
<small className="text-muted">{employee.jobRole}</small>
</td>
</tr>
))

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

@ -12,8 +12,13 @@ import EmployeeList from "./EmployeeList";
import { useAllEmployees, useEmployees } from "../../hooks/useEmployees";
import { useSortableData } from "../../hooks/useSortableData";
import ConfirmModal from "../common/ConfirmModal";
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
import {DIRECTORY_ADMIN, DIRECTORY_MANAGER} from "../../utils/constants";
import {useProfile} from "../../hooks/useProfile";
const ManageBucket = () => {
const ManageBucket = () =>
{
const {profile} =useProfile()
const [bucketList, setBucketList] = useState([]);
const { employeesList } = useAllEmployees(false);
const [selectedEmployee, setSelectEmployee] = useState([]);
@ -22,7 +27,9 @@ const ManageBucket = () => {
const [isSubmitting, setSubmitting] = useState(false);
const [selected_bucket, select_bucket] = useState(null);
const [deleteBucket, setDeleteBucket] = useState(null);
const [searchTerm, setSearchTerm] = useState("");
const [ searchTerm, setSearchTerm ] = useState( "" );
const DirManager = useHasUserPermission( DIRECTORY_MANAGER )
const DirAdmin = useHasUserPermission(DIRECTORY_ADMIN)
const {
items: sortedBuckteList,
requestSort,
@ -336,19 +343,25 @@ const ManageBucket = () => {
</td>
<td>{bucket.numberOfContacts}</td>
<td className="justify-content-center">
<div className="d-flex justify-content-center align-items-center gap-2">
<i
className="bx bx-edit bx-sm text-primary cursor-pointer"
onClick={() => {
select_bucket(bucket);
setAction_bucket(true);
{(DirManager ||
DirAdmin ||
bucket?.createdBy?.id ===
profile?.employeeInfo?.id) && (
<div className="d-flex justify-content-center align-items-center gap-2">
<i
className="bx bx-edit bx-sm text-primary cursor-pointer "
onClick={() => {
select_bucket(bucket);
setAction_bucket(true);
}}
></i>
<i
className="bx bx-trash bx-sm text-danger cursor-pointer"
onClick={() => setDeleteBucket(bucket?.id)}
></i>
</div>
></i>
<i
className="bx bx-trash bx-sm text-danger cursor-pointer"
onClick={() => setDeleteBucket(bucket?.id)}
></i>
</div>
)}
</td>
</tr>
))}
@ -386,7 +399,7 @@ const ManageBucket = () => {
<EmployeeList
employees={employeesList}
onChange={(data) => setSelectEmployee(data)}
assignedEmployee={selected_bucket?.employeeIds}
bucket={selected_bucket}
/>
)}

View File

@ -17,24 +17,22 @@ import { changeMaster } from "../../slices/localVariablesSlice";
import { useBuckets, useOrganization } from "../../hooks/useDirectory";
import { useProjects } from "../../hooks/useProjects";
import SelectMultiple from "../common/SelectMultiple";
import {ContactSchema} from "./DirectorySchema";
import { ContactSchema } from "./DirectorySchema";
import InputSuggestions from "../common/InputSuggestion";
const ManageDirectory = ({ submitContact, onCLosed }) => {
const selectedMaster = useSelector(
(store) => store.localVariables.selectedMaster
);
const [ categoryData, setCategoryData ] = useState( [] );
const [categoryData, setCategoryData] = useState([]);
const [TagsData, setTagsData] = useState([]);
const { data, loading } = useMaster();
const { buckets, loading: bucketsLoaging } = useBuckets();
const { projects, loading: projectLoading } = useProjects();
const { contactCategory, loading: contactCategoryLoading } =
useContactCategory();
const {organizationList,loading:orgLoading} = useOrganization()
const { organizationList, loading: orgLoading } = useOrganization();
const { contactTags, loading: Tagloading } = useContactTags();
const [IsSubmitting, setSubmitting] = useState(false);
const dispatch = useDispatch();
@ -79,15 +77,14 @@ const ManageDirectory = ({ submitContact, onCLosed }) => {
remove: removePhone,
} = useFieldArray({ control, name: "contactPhones" });
useEffect(() => {
if (emailFields.length === 0) {
appendEmail({ label: "Work", emailAddress: "" });
}
if (phoneFields.length === 0) {
appendPhone({ label: "Office", phoneNumber: "" });
}
}, [emailFields.length, phoneFields.length]);
useEffect(() => {
if (emailFields.length === 0) {
appendEmail({ label: "Work", emailAddress: "" });
}
if (phoneFields.length === 0) {
appendPhone({ label: "Office", phoneNumber: "" });
}
}, [emailFields.length, phoneFields.length]);
const handleAddEmail = async () => {
const emails = getValues("contactEmails");
@ -124,22 +121,21 @@ useEffect(() => {
setValue("bucketIds", updated, { shouldValidate: true });
};
const onSubmit = ( data ) =>
{
const cleaned = {
...data,
contactEmails: (data.contactEmails || []).filter(
(e) => e.emailAddress?.trim() !== ""
),
contactPhones: (data.contactPhones || []).filter(
(p) => p.phoneNumber?.trim() !== ""
),
};
const onSubmit = (data) => {
const cleaned = {
...data,
contactEmails: (data.contactEmails || []).filter(
(e) => e.emailAddress?.trim() !== ""
),
contactPhones: (data.contactPhones || []).filter(
(p) => p.phoneNumber?.trim() !== ""
),
};
setSubmitting(true);
submitContact(cleaned, reset, setSubmitting);
};
const orgValue = watch("organization")
const orgValue = watch("organization");
const handleClosed = () => {
onCLosed();
@ -165,16 +161,11 @@ useEffect(() => {
<div className="col-md-6 text-start">
<label className="form-label">Organization</label>
<InputSuggestions
organizationList={organizationList}
value={getValues("organization") || ""}
onChange={(val) => setValue("organization", val)}
error={errors.organization?.message}
/>
{errors.organization && (
<small className="danger-text">
{errors.organization.message}
</small>
)}
organizationList={organizationList}
value={getValues("organization") || ""}
onChange={(val) => setValue("organization", val)}
error={errors.organization?.message}
/>
</div>
</div>
<div className="row mt-1">
@ -216,7 +207,10 @@ useEffect(() => {
// onClick={handleAddEmail}
// style={{ width: "24px", height: "24px" }}
// >
<i className="bx bx-plus-circle bx-xs ms-1 cursor-pointer text-primary" onClick={handleAddEmail} />
<i
className="bx bx-plus-circle bx-xs ms-1 cursor-pointer text-primary"
onClick={handleAddEmail}
/>
) : (
// <button
// type="button"
@ -224,8 +218,10 @@ useEffect(() => {
// onClick={() => removeEmail(index)}
// style={{ width: "24px", height: "24px" }}
// >
<i className="bx bx-minus-circle bx-xs ms-1 cursor-pointer text-primary" onClick={() => removeEmail(index)} />
<i
className="bx bx-minus-circle bx-xs ms-1 cursor-pointer text-primary"
onClick={() => removeEmail(index)}
/>
)}
</div>
{errors.contactEmails?.[index]?.emailAddress && (
@ -275,8 +271,10 @@ useEffect(() => {
// onClick={handleAddPhone}
// style={{ width: "24px", height: "24px" }}
// >
<i className="bx bx-plus-circle bx-xs ms-1 cursor-pointer text-primary" onClick={handleAddPhone} />
<i
className="bx bx-plus-circle bx-xs ms-1 cursor-pointer text-primary"
onClick={handleAddPhone}
/>
) : (
// <button
// type="button"
@ -284,8 +282,10 @@ useEffect(() => {
// onClick={() => removePhone(index)}
// style={{ width: "24px", height: "24px" }}
// >
<i className="bx bx-minus-circle bx-xs ms-1 cursor-pointer text-danager" onClick={() => removePhone(index)} />
<i
className="bx bx-minus-circle bx-xs ms-1 cursor-pointer text-danager"
onClick={() => removePhone(index)}
/>
)}
</div>
{errors.contactPhones?.[index]?.phoneNumber && (
@ -297,9 +297,9 @@ useEffect(() => {
</div>
))}
</div>
{errors.contactPhone?.message && (
<div className="danger-text">{errors.contactPhone.message}</div>
)}
{errors.contactPhone?.message && (
<div className="danger-text">{errors.contactPhone.message}</div>
)}
</div>
<div className="row my-1">
@ -315,7 +315,7 @@ useEffect(() => {
</option>
) : (
<>
<option disabled value="">
<option disabled value="">
Select Category
</option>
{contactCategory?.map((cate) => (
@ -341,29 +341,23 @@ useEffect(() => {
valueKey="id"
IsLoading={projectLoading}
/>
{errors.projectIds && (
<small className="danger-text">
{errors.projectIds.message}
</small>
{errors.projectIds && (
<small className="danger-text">{errors.projectIds.message}</small>
)}
</div>
</div>
<div className="col-12 text-start">
<TagInput name="tags" label="Tags" options={contactTags} />
{errors.tags && (
<small className="danger-text">
{errors.tags.message}
</small>
)}
{errors.tags && (
<small className="danger-text">{errors.tags.message}</small>
)}
</div>
<div className="row">
<div className="col-md-12 mt-1 text-start">
<label className="form-label ">Select Label</label>
<label className="form-label ">Select Bucket</label>
<ul
className="d-flex flex-wrap px-1 list-unstyled mb-0"
>
<ul className="d-flex flex-wrap px-1 list-unstyled mb-0">
{bucketsLoaging && <p>Loading...</p>}
{buckets?.map((item) => (
<li
@ -387,11 +381,12 @@ useEffect(() => {
</div>
</li>
))}
{errors.bucketIds && (
<small className="danger-text mt-0">
{errors.bucketIds.message}
</small>
)}
</ul>
{errors.BucketIds && (
<small className="text-danger">{errors.BucketIds.message}</small>
)}
</div>
</div>

View File

@ -7,11 +7,12 @@ import showToast from "../../services/toastService";
import { cacheData, getCachedData } from "../../slices/apiDataManager";
import "../common/TextEditor/Editor.css";
const NoteCardDirectory = ({ IsActive,noteItem, contactId, setProfileContact }) => {
const NoteCardDirectory = ({refetchProfile,refetchNotes, noteItem, contactId, setProfileContact, }) => {
const [editing, setEditing] = useState(false);
const [editorValue, setEditorValue] = useState(noteItem.note);
const [isLoading, setIsLoading] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const [ isDeleting, setIsDeleting ] = useState( false );
const [isActivProcess,setActiveProcessing]= useState(false)
const handleUpdateNote = async () => {
try {
setIsLoading(true);
@ -63,10 +64,10 @@ const NoteCardDirectory = ({ IsActive,noteItem, contactId, setProfileContact })
}
};
const handleDeleteNote = async () => {
const handleDeleteNote = async (activeStatue) => {
try {
setIsDeleting(true);
const resp = await DirectoryRepository.DeleteNote(noteItem.id);
activeStatue ? setActiveProcessing(true) : setIsDeleting(true)
const resp = await DirectoryRepository.DeleteNote(noteItem.id,activeStatue);
setProfileContact((prev) => ({
...prev,
notes: prev.notes.filter((note) => note.id !== noteItem.id),
@ -90,8 +91,11 @@ const NoteCardDirectory = ({ IsActive,noteItem, contactId, setProfileContact })
cacheData("Contact Profile", updatedCache);
}
setIsDeleting(false);
showToast("Note Deleted Successfully", "success");
setIsDeleting( false );
setActiveProcessing( false )
refetchNotes( contactId, false )
refetchProfile(contactId)
showToast(`Note ${activeStatue ? "Restored":"Deleted"} Successfully`, "success");
} catch (error) {
setIsDeleting(false);
const msg =
@ -101,13 +105,13 @@ const NoteCardDirectory = ({ IsActive,noteItem, contactId, setProfileContact })
showToast(msg, "error");
}
};
return (
<div
className="card p-1 shadow-sm border-1 mb-2 conntactNote"
style={{ width: "100%", minWidth: "300px", borderRadius: "0px", background: `${!IsActive ? "#f8f6f6" : ""}` }}
style={{ width: "100%", minWidth: "300px", borderRadius: "0px", background: `${noteItem.isActive ? "": "#f8f6f6"}` }}
key={noteItem.id}
>
<div className="d-flex justify-content-between align-items-center mb-1">
<div className="d-flex align-items-center">
<Avatar
@ -129,26 +133,39 @@ const NoteCardDirectory = ({ IsActive,noteItem, contactId, setProfileContact })
</span>
</div>
</div>
<div className={`${IsActive ? " ":"d-none"}`}>
<i
className="bx bxs-edit bx-sm me-1 text-primary cursor-pointer"
onClick={() => setEditing(true)}
></i>
{!isDeleting && (
<i
className="bx bx-trash bx-sm me-1 text-secondary cursor-pointer"
onClick={handleDeleteNote}
></i>
)}
{isDeleting && (
<div
class="spinner-border spinner-border-sm text-secondary"
role="status"
>
<span class="visually-hidden">Loading...</span>
</div>
)}
<div>
{noteItem.isActive ? (
<>
<i
className="bx bxs-edit bx-sm me-1 text-primary cursor-pointer"
onClick={() => setEditing(true)}
></i>
{!isDeleting ? (
<i
className="bx bx-trash bx-sm me-1 text-secondary cursor-pointer"
onClick={() => handleDeleteNote(!noteItem.isActive)}
></i>
) : (
<div
className="spinner-border spinner-border-sm text-secondary"
role="status"
>
<span className="visually-hidden">Loading...</span>
</div>
)}
</>
) : isActivProcess ? (
< i className='bx bx-refresh text-primary bx-spin' ></i>
) : (
<i
className="bx bx-history me-1 text-primary cursor-pointer"
onClick={() => handleDeleteNote(!noteItem.isActive)}
title="Restore"
></i>
)}
</div>
</div>
<hr className="mt-0" />

View File

@ -16,9 +16,9 @@ const schema = z.object({
note: z.string().min(1, { message: "Note is required" }),
});
const NotesDirectory = ({ isLoading, contactProfile, setProfileContact }) => {
const NotesDirectory = ({refetchProfile, isLoading, contactProfile, setProfileContact }) => {
const [IsActive, setIsActive] = useState(true);
const { contactNotes } = useContactNotes(contactProfile?.id, !IsActive);
const {contactNotes,refetch} = useContactNotes( contactProfile?.id, true );
const [NotesData, setNotesData] = useState();
const [IsSubmitting, setIsSubmitting] = useState(false);
@ -85,6 +85,14 @@ const NotesDirectory = ({ isLoading, contactProfile, setProfileContact }) => {
const onCancel = () => {
setValue("note", "");
};
const handleSwitch = () =>
{
setIsActive( !IsActive )
if ( IsActive )
{
refetch(contactProfile?.id, false)
}
}
return (
<div className="text-start">
@ -95,7 +103,7 @@ const NotesDirectory = ({ isLoading, contactProfile, setProfileContact }) => {
<input
type="checkbox"
className="switch-input"
onChange={() => setIsActive(!IsActive)}
onChange={()=>handleSwitch(!IsActive)}
value={IsActive}
/>
<span className="switch-toggle-slider">
@ -106,9 +114,13 @@ const NotesDirectory = ({ isLoading, contactProfile, setProfileContact }) => {
{/* <i class="icon-base bx bx-x"></i> */}
</span>
</span>
<span className="switch-label small-text">Show Inactive Notes</span>
<span className="switch-label small-text">Show Including Inactive Notes</span>
</label>
<span
</div>
</div>
<div className="d-flex justify-content-end px-2">
<span
className={`btn btn-xs ${addNote ? "btn-danger" : "btn-primary"}`}
onClick={() => setAddNote(!addNote)}
>
@ -119,7 +131,6 @@ const NotesDirectory = ({ isLoading, contactProfile, setProfileContact }) => {
></i> */}
{addNote ? "close" : "Add Note"}
</span>
</div>
</div>
{addNote && (
<form onSubmit={handleSubmit(onSubmit)}>
@ -143,30 +154,34 @@ const NotesDirectory = ({ isLoading, contactProfile, setProfileContact }) => {
</div>
)}
{!isLoading &&
[...(IsActive ? contactProfile?.notes || [] : contactNotes || [])]
.reverse()
.map((noteItem) => (
<NoteCardDirectory
IsActive={IsActive}
noteItem={noteItem}
contactId={contactProfile?.id}
setProfileContact={setProfileContact}
key={noteItem.id}
/>
))}
[...(IsActive ? contactProfile?.notes || [] : contactNotes || [])]
.reverse()
.map((noteItem) => (
<NoteCardDirectory
refetchProfile={refetchProfile}
refetchNotes={refetch}
refetchContact ={refetch}
noteItem={noteItem}
contactId={contactProfile?.id}
setProfileContact={setProfileContact}
key={noteItem.id}
/>
))}
{IsActive && (
<p>
<div>
{!isLoading && contactProfile?.notes.length == 0 && !addNote && (
<p className="text-center">No Notes Found</p>
<div className="text-center">No Notes Found</div>
)}
</p>
</div>
)}
{!IsActive && (
<p>
<div>
{!isLoading && contactNotes.length == 0 && !addNote && (
<p className="text-center">No Notes Found</p>
<div className="text-center">No Notes Found</div>
)}
</p>
</div>
)}
</div>
</div>

View File

@ -5,12 +5,28 @@ import moment from "moment";
import NotesDirectory from "./NotesDirectory";
const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
const { conatProfile, loading } = useContactProfile(contact?.id);
const [profileContact, setProfileContact] = useState();
const { contactProfile, loading, refetch } = useContactProfile(contact?.id);
const [copiedIndex, setCopiedIndex] = useState(null);
const [profileContact, setProfileContact] = useState();
const [expanded, setExpanded] = useState(false);
const description = contactProfile?.description || "";
const limit = 100;
const toggleReadMore = () => setExpanded(!expanded);
const isLong = description.length > limit;
const displayText = expanded
? description
: description.slice(0, limit) + (isLong ? "..." : "");
useEffect(() => {
setProfileContact(conatProfile);
}, [conatProfile]);
setProfileContact(contactProfile);
}, [contactProfile]);
const handleCopy = (email, index) => {
navigator.clipboard.writeText(email);
setCopiedIndex(index);
setTimeout(() => setCopiedIndex(null), 2000); // Reset after 2 seconds
};
return (
<div className="p-1">
<div className="text-center m-0 p-0">
@ -20,6 +36,7 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
<div className="d-flex align-items-center mb-2">
<Avatar
size="sm"
classAvatar="m-0"
firstName={
(contact?.name || "").trim().split(" ")[0]?.charAt(0) || ""
}
@ -27,92 +44,187 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
(contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""
}
/>
<div className="d-flex flex-column text-start ms-2">
<div className="d-flex flex-column text-start ms-1">
<span className="m-0 fw-semibold">{contact?.name}</span>
<span className="small">
<i className="bx bx-building bx-xs"></i>{" "}
{conatProfile?.organization}
</span>
<span className="small-text">Manager</span>
<small className="text-secondary small-text">
{contactProfile?.tags?.map((tag) => tag.name).join(" | ")}
</small>
</div>
</div>
<div className="row">
<div className="col-12 col-md-6 d-flex flex-column text-start">
{conatProfile?.contactEmails?.length > 0 && (
<div className="d-flex mb-2">
<div style={{ width: "100px", minWidth: "100px" }}>
<p className="m-0">Email</p>
<div className="col-12 col-md-6 d-flex flex-column text-start">
{contactProfile?.contactEmails?.length > 0 && (
<div className="d-flex mb-2">
<div style={{ width: "100px", minWidth: "100px" }}>
<p className="m-0">Email:</p>
</div>
<div>
<ul className="list-inline mb-0">
{contactProfile.contactEmails.map((email, idx) => (
<li
className="list-inline-item me-3 d-flex align-items-center"
key={idx}
>
<i className="bx bx-envelope bx-xs me-1"></i>
<span className="me-2">{email.emailAddress}</span>
<i
className={`bx bx-copy cursor-pointer ${
copiedIndex === idx
? "text-secondary"
: "text-primary"
}`}
title={copiedIndex === idx ? "Copied!" : "Copy Email"}
style={{ fontSize: "16px" }}
onClick={() => handleCopy(email.emailAddress, idx)}
></i>
</li>
))}
</ul>
</div>
</div>
<div>
<ul className="list-inline mb-0">
{conatProfile.contactEmails.map((email, idx) => (
<li className="list-inline-item me-3" key={idx}>
<i className="bx bx-envelope bx-xs me-1"></i>
{email.emailAddress}
</li>
))}
</ul>
</div>
</div>
)}
{conatProfile?.contactPhones?.length > 0 && (
<div className="d-flex mb-2">
<div style={{ width: "100px", minWidth: "100px" }}>
<p className="m-0">Phone</p>
</div>
<div>
<ul className="list-inline mb-0">
{conatProfile?.contactPhones.map((phone, idx) => (
<li className="list-inline-item me-3" key={idx}>
<i className="bx bx-phone bx-xs me-1"></i>
{phone.phoneNumber}
</li>
))}
</ul>
</div>
</div>
)}
)}
{conatProfile?.createdAt && (
<div className="d-flex mb-2">
<div style={{ width: "100px", minWidth: "100px" }}>
<p className="m-0">Created</p>
{contactProfile?.contactPhones?.length > 0 && (
<div className="d-flex mb-2">
<div style={{ width: "100px", minWidth: "100px" }}>
<p className="m-0">Phone : </p>
</div>
<div>
<ul className="list-inline mb-0">
{contactProfile?.contactPhones.map((phone, idx) => (
<li className="list-inline-item me-3" key={idx}>
<i className="bx bx-phone bx-xs me-1"></i>
{phone.phoneNumber}
</li>
))}
</ul>
</div>
</div>
<div>
<ul className="list-inline mb-0">
<li className="list-inline-item">
<i className="bx bx-calendar-week bx-xs me-1"></i>
{moment(conatProfile.createdAt).format("MMMM, DD YYYY")}
</li>
</ul>
)}
{contactProfile?.createdAt && (
<div className="d-flex mb-2">
<div style={{ width: "100px", minWidth: "100px" }}>
<p className="m-0">Created : </p>
</div>
<div>
<ul className="list-inline mb-0">
<li className="list-inline-item">
<i className="bx bx-calendar-week bx-xs me-1"></i>
{moment(contactProfile.createdAt).format("MMMM, DD YYYY")}
</li>
</ul>
</div>
</div>
</div>
)}
)}
{contactProfile?.address && (
<div className="d-flex mb-2">
<div style={{ width: "100px", minWidth: "100px" }}>
<p className="m-0">Location : </p>
</div>
<div>
<ul className="list-inline mb-0">
<li className="list-inline-item">
<i className="bx bx-map bx-xs me-1"></i>
{contactProfile?.address}
</li>
</ul>
</div>
</div>
)}
</div>
{ conatProfile?.buckets?.length > 0 &&
<div className="col-12 col-md-6 d-flex flex-column text-start">
{conatProfile?.contactEmails?.length > 0 && (
<div className="d-flex mb-2 align-items-center">
<div style={{ width: "100px", minWidth: "100px" }}>
<p className="m-0">Buckets</p>
</div>
<div>
<ul className="list-inline mb-0">
{conatProfile.buckets.map((bucket) => (
<li className="list-inline-item me-2" key={bucket.id}>
<span class="badge bg-label-primary my-1">{ bucket.name}</span>
</li>
))}
</ul>
</div>
</div>
)}
</div>
}
</div>
<div className="col-12 col-md-6 d-flex flex-column text-start">
{contactProfile?.organization && (
<div className="d-flex mb-2">
<div style={{ width: "100px", minWidth: "100px" }}>
<p className="m-0">Orgnization : </p>
</div>
<div>
<ul className="list-inline mb-0">
<li className="list-inline-item">
<i className="fa-solid fa-briefcase me-2"></i>
{contactProfile.organization}
</li>
</ul>
</div>
</div>
)}
{contactProfile?.contactCategory && (
<div className="d-flex mb-2">
<div style={{ width: "100px", minWidth: "100px" }}>
<p className="m-0">Category : </p>
</div>
<div>
<ul className="list-inline mb-0">
<li className="list-inline-item">
<i className="bx bx-user bx-xs me-1"></i>
{contactProfile.contactCategory.name}
</li>
</ul>
</div>
</div>
)}
{contactProfile?.buckets?.length > 0 && (
<div className="d-flex ">
{contactProfile?.contactEmails?.length > 0 && (
<div className="d-flex mb-2 align-items-center">
<div style={{ width: "100px", minWidth: "100px" }}>
<p className="m-0">Buckets : </p>
</div>
<div>
<ul className="list-inline mb-0">
{contactProfile.buckets.map((bucket) => (
<li className="list-inline-item me-2" key={bucket.id}>
<span className="badge bg-label-primary my-1">
{bucket.name}
</span>
</li>
))}
</ul>
</div>
</div>
)}
</div>
)}
</div>
</div>
{contactProfile?.projects?.length > 0 && (
<div className="d-flex mb-2 align-items-start">
<div style={{ minWidth: "100px" }}>
<p className="m-0 text-start">Projects :</p>
</div>
<div className="text-start">
<ul className="list-inline mb-0">
{contactProfile.projects.map((project, index) => (
<li className="list-inline-item me-2" key={project.id}>
{project.name}
{index < contactProfile.projects.length - 1 && ","}
</li>
))}
</ul>
</div>
</div>
)}
<div className="p-3 alert alert-secondary text-small text-start rounded muted">
<small className="fw-130">
{displayText}
{isLong && (
<span
onClick={toggleReadMore}
className="text-primary mx-1 cursor-pointer"
>
{expanded ? "Read less" : "Read more"}
</span>
)}
</small>
</div>
<hr className="my-1" />
<NotesDirectory
refetchProfile={refetch}
isLoading={loading}
contactProfile={profileContact}
setProfileContact={setProfileContact}

View File

@ -387,13 +387,10 @@ await submitContact({ ...cleaned, id: existingContact.id });
)}
</div>
<div className="row">
<div className="col-md-12 mt-1 text-start">
<div className="col-md-12 mt-1 text-start">
<label className="form-label ">Select Label</label>
<ul
className="d-flex flex-wrap px-1 list-unstyled mb-0"
>
<ul className="d-flex flex-wrap px-1 list-unstyled mb-0">
{bucketsLoaging && <p>Loading...</p>}
{buckets?.map((item) => (
<li
@ -417,11 +414,12 @@ await submitContact({ ...cleaned, id: existingContact.id });
</div>
</li>
))}
{errors.bucketIds && (
<small className="danger-text mt-0">
{errors.bucketIds.message}
</small>
)}
</ul>
{errors.BucketIds && (
<small className="text-danger">{errors.BucketIds.message}</small>
)}
</div>
</div>

View File

@ -3,7 +3,7 @@ const Footer = () => {
<>
<footer className="content-footer footer bg-footer-theme">
<div className="container-xxl d-flex flex-wrap justify-content-between py-2 flex-md-row flex-column">
<div className="mb-2 mb-md-0" style={{width: "100%", textAlign: "right"}}>
<div className="mb-2 mb-md-0 small-text" style={{width: "100%", textAlign: "right"}}>
© {new Date().getFullYear()}
, by <a href="https://marcosolutions.co.in/" target="_blank" className="font-weight-light footer-link">MARCO AIoT Technologies Pvt. Ltd.</a>
</div>

View File

@ -60,7 +60,7 @@ useEffect(() => {
};
const handleInputKeyDown = (e) => {
if (e.key === "Enter" && input.trim() !== "") {
if ((e.key === "Enter" || e.key === " ")&& input.trim() !== "") {
e.preventDefault();
const existing = options.find(
(opt) => opt.name.toLowerCase() === input.trim().toLowerCase()

View File

@ -29,21 +29,22 @@ const Editor = ({
"code-block",
"link",
"align",
"image"
"image",
];
return (
<div className="editor-wrapper">
<div id="custom-toolbar" className="ql-toolbar ql-snow custom-toolbar">
<div id="custom-toolbar" className="ql-toolbar ql-snow custom-toolbar">
<div className="d-flex justify-content-between align-items-center w-100">
{/* Left: Quill Format Buttons */}
<span className="d-flex">
<span className="ql-formats">
<select className="ql-header">
<select className="ql-header" defaultValue="">
<option value="1" />
<option value="2" />
<option selected />
<option value="" />
</select>
<button className="ql-bold" />
<button className="ql-italic" />
<button className="ql-underline" />
@ -60,8 +61,6 @@ const Editor = ({
<button className="ql-link" />
</span>
</span>
</div>
</div>
<ReactQuill
@ -72,21 +71,24 @@ const Editor = ({
theme="snow"
placeholder={placeholder}
/>
{/* Right: Submit + Cancel Buttons */}
<div className="d-flex justify-content-end gap-2 p-1">
<span className="btn btn-xs btn-secondary" aria-disabled={loading} onClick={onCancel}>
Cancel
</span>
<span
type="submit"
className="btn btn-xs btn-primary"
onClick={onSubmit}
aria-disabled={loading}
>
{loading ? "Please Wait..." : "Submit"}
</span>
</div>
{/* Right: Submit + Cancel Buttons */}
<div className="d-flex justify-content-end gap-2 p-1">
<span
className="btn btn-xs btn-secondary"
aria-disabled={loading}
onClick={onCancel}
>
Cancel
</span>
<span
type="submit"
className="btn btn-xs btn-primary"
onClick={onSubmit}
aria-disabled={loading}
>
{loading ? "Please Wait..." : "Submit"}
</span>
</div>
</div>
);
};

View File

@ -23,11 +23,6 @@
"text": "Employees",
"available": true,
"link": "/employees"
},
{
"text": "Directory",
"available": true,
"link": "/directory"
}
]
},
@ -68,6 +63,12 @@
"link": "/activities/gallary"
}
]
},
{
"text": "Directory",
"icon": "bx bx-folder",
"available": true,
"link": "/directory"
},
{
"text": "Administration",

View File

@ -72,14 +72,12 @@ export const useBuckets = () => {
};
export const useContactProfile = (id) => {
const [conatProfile, setContactProfile] = useState(null);
const [contactProfile, setContactProfile] = useState(null);
const [loading, setLoading] = useState(false);
const [Error, setError] = useState("");
const fetchContactProfile = async () => {
const cached = getCachedData("Contact Profile");
if (!cached || cached.contactId !== id) {
setLoading(true);
try {
const resp = await DirectoryRepository.GetContactProfile(id);
@ -94,18 +92,20 @@ export const useContactProfile = (id) => {
} finally {
setLoading(false);
}
};
useEffect( () =>
{
const cached = getCachedData("Contact Profile");
if (!cached || cached.contactId !== id) {
fetchContactProfile(id);
} else {
setContactProfile(cached.data);
}
};
useEffect(() => {
if (id) {
fetchContactProfile(id);
}
}, [id]);
return { conatProfile, loading, Error };
return { contactProfile, loading, Error ,refetch:fetchContactProfile};
};
export const useContactNotes = (id, IsActive) => {
@ -113,10 +113,9 @@ export const useContactNotes = (id, IsActive) => {
const [loading, setLoading] = useState(false);
const [Error, setError] = useState("");
const fetchContactNotes = async () => {
const cached = getCachedData("Contact Notes");
const fetchContactNotes = async (id,IsActive) => {
if (!cached || cached.contactId !== id) {
setLoading(true);
try {
const resp = await DirectoryRepository.GetNote(id, IsActive);
@ -131,18 +130,19 @@ export const useContactNotes = (id, IsActive) => {
} finally {
setLoading(false);
}
} else {
setContactNotes(cached.data);
}
};
useEffect(() => {
if (id) {
fetchContactNotes(id);
const cached = getCachedData("Contact Notes");
if (!cached || cached.contactId !== id) {
id && fetchContactNotes(id,IsActive);
} else {
setContactNotes(cached.data);
}
}, [id]);
}, [id,IsActive]);
return { contactNotes, loading, Error };
return { contactNotes, loading, Error,refetch:fetchContactNotes };
};
export const useOrganization = () => {

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>
);
};

View File

@ -1,14 +1,12 @@
import React from 'react'
import IconButton from '../../components/common/IconButton';
import React from "react";
import IconButton from "../../components/common/IconButton";
const DirectoryListTableHeader = ( {children, IsActive} ) =>
{
const DirectoryListTableHeader = ({ children }) => {
return (
<div className="table-responsive text-nowrap py-2">
<table className="table px-2">
<thead>
<tr>
<tr>
<th colSpan={2}>
<div className="d-flex align-items-center gap-1">
<span>Name</span>
@ -25,13 +23,10 @@ const DirectoryListTableHeader = ( {children, IsActive} ) =>
</div>
</th>
<th colSpan={2} className="mx-2 ps-20">
Organization
Organization
</th>
<th className="mx-2">Category</th>
{IsActive && <th>Action</th>}
<th>Action</th>
</tr>
</thead>
<tbody className="table-border-bottom-0 overflow-auto">
@ -41,4 +36,4 @@ const DirectoryListTableHeader = ( {children, IsActive} ) =>
</div>
);
};
export default DirectoryListTableHeader;
export default DirectoryListTableHeader;

View File

@ -16,7 +16,6 @@ const DirectoryPageHeader = ({
applyFilter,
loading,
IsActive,
setIsOpenModal,
setOpenBucketModal,
}) => {
const [filtered, setFiltered] = useState();
@ -99,7 +98,7 @@ const DirectoryPageHeader = ({
<div className="d-flex flex-wrap">
{filteredBuckets.map(({ id, name }) => (
<div
className="form-check me-1 mb-1"
className="form-check me-3 mb-1"
style={{ minWidth: "33.33%" }}
key={id}
>
@ -127,7 +126,7 @@ const DirectoryPageHeader = ({
<div className="d-flex flex-wrap">
{filteredCategories.map(({ id, name }) => (
<div
className="form-check me-1 mb-1"
className="form-check me-3 mb-1"
style={{ minWidth: "33.33%" }}
key={id}
>
@ -169,38 +168,10 @@ const DirectoryPageHeader = ({
</div>
</div>
<div className="col-12 col-md-8 mb-2 px-1 d-flex justify-content-end gap-2 align-items-center text-end">
<button
type="button"
className="btn btn-sm btn-primary"
onClick={() => setIsOpenModal(true)}
>
<i className="bx bx-plus-circle me-2"></i>
New Contact
</button>
<div className={`dropdown `}>
<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-md text-muted "
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>
<a className="dropdown-item px-2 ">
<label className="switch switch-primary align-self-start mb-2">
<label className="switch switch-primary align-self-start mb-2">
<input
type="checkbox"
className="switch-input"
className="switch-input me-3"
onChange={() => setIsActive(!IsActive)}
value={IsActive}
disabled={loading}
@ -209,23 +180,10 @@ const DirectoryPageHeader = ({
<span className="switch-on"></span>
<span className="switch-off"></span>
</span>
<span className=" list-inline-item ">
<span className=" list-inline-item ms-12 small-text">
Show Inactive Contacts
</span>
</label>
</a>
</li>
<li>
<a
className="dropdown-item cursor-pointer px-2 "
onClick={() => setOpenBucketModal(true)}
>
<i className="fa-solid fa-bucket fs-5 me-4"></i>
<span className="align-left">Manage Buckets</span>
</a>
</li>
</ul>
</div>
</div>
</div>
</>

View File

@ -6,7 +6,7 @@ export const DirectoryRepository = {
GetContacts: (isActive) => api.get( `/api/directory?active=${isActive}` ),
CreateContact: ( data ) => api.post( '/api/directory', data ),
UpdateContact: ( id, data ) => api.put( `/api/directory/${ id }`, data ),
DeleteContact: ( id ) => api.delete( `/api/directory/${ id }` ),
DeleteContact: ( id,isActive) => api.delete( `/api/directory/${ id }/?active=${isActive}` ),
AssignedBuckets:(id,data)=>api.post(`/api/directory/assign-bucket/${id}`,data),
GetBucktes: () => api.get( `/api/directory/buckets` ),
@ -19,5 +19,5 @@ export const DirectoryRepository = {
CreateNote: ( data ) => api.post( '/api/directory/note', data ),
GetNote: ( id,isActive ) => api.get( `/api/directory/notes/${ id }?active=${isActive}` ),
UpdateNote: ( id, data ) => api.put( `/api/directory/note/${ id }`, data ),
DeleteNote:(id)=> api.delete(`/api/directory/note/${ id }`)
DeleteNote:(id,isActive)=> api.delete(`/api/directory/note/${ id }?active=${isActive}`)
}