Compare commits

..

No commits in common. "6fa0107792f19dc48fe3255ebda1e7abb54121f3" and "8c66a9a08317f797e7cea8e51f9f8ab8ab7dc108" have entirely different histories.

21 changed files with 480 additions and 707 deletions

View File

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

View File

@ -1,21 +0,0 @@
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,7 +3,6 @@ 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,
@ -12,10 +11,8 @@ 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,70 +30,54 @@ const CardViewDirectory = ({
(contact?.name || "").trim().split(" ")[1]?.charAt(0) || "" (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> <div>
{IsActive && ( <div className={`dropdown z-2 ${!IsActive && "d-none"}`}>
<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" data-bs-toggle="dropdown"
data-bs-toggle="dropdown" aria-expanded="false"
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);
}}
> >
<i <a className="dropdown-item px-2 cursor-pointer py-1">
className="bx bx-dots-vertical-rounded bx-sm text-muted p-0" <i className="bx bx-edit bx-xs text-primary me-2"></i>
data-bs-toggle="tooltip" <span className="align-left ">Modify</span>
data-bs-offset="0,8" </a>
data-bs-placement="top" </li>
data-bs-custom-class="tooltip-dark" <li>
title="More Action" <a
></i> className="dropdown-item px-2 cursor-pointer py-1"
</button> onClick={() => IsDeleted(contact.id)}
<ul className="dropdown-menu dropdown-menu-end w-auto">
<li
onClick={() => {
setSelectedContact(contact);
setIsOpenModal(true);
}}
> >
<a className="dropdown-item px-2 cursor-pointer py-1"> <i className="bx bx-trash text-danger bx-xs me-2"></i>
<i className="bx bx-edit bx-xs text-primary me-2"></i> <span className="align-left">Delete</span>
<span className="align-left ">Modify</span> </a>
</a> </li>
</li> </ul>
<li> </div>
<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>
</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="fa-solid fa-briefcase me-2"></i> <i className="bx bx-building bx-xs"></i>
</li> </li>
<li className="list-inline-item text-small"> <li className="list-inline-item text-small">
{contact.organization} {contact.organization}
@ -116,7 +97,7 @@ const CardViewDirectory = ({
{contact.contactEmails[0] && ( {contact.contactEmails[0] && (
<ul className="list-inline my-1 "> <ul className="list-inline my-1 ">
<li className="list-inline-item me-2"> <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>
<li className="list-inline-item text-small"> <li className="list-inline-item text-small">
{contact.contactEmails[0]?.emailAddress} {contact.contactEmails[0]?.emailAddress}
@ -141,28 +122,27 @@ 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}
</li> </li>
</ul> </ul>
<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 <span className="badge bg-label-primary rounded-pill d-flex align-items-center gap-1" style={{padding:'0.1rem 0.3rem'}}>
className="badge bg-label-primary rounded-pill d-flex align-items-center gap-1" <i className="bx bx-pin bx-xs"></i>
style={{ padding: "0.1rem 0.3rem" }} <span className="small-text">
> {getBucketNameById(buckets, bucketId)}
<i className="bx bx-pin bx-xs"></i> </span>
<span className="small-text"> </span>
{getBucketNameById(buckets, bucketId)} </li>
</span> ))}
</span> </ul>
</li>
))}
</ul>
</div> </div>
</div> </div>
); );

View File

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

View File

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

View File

@ -1,128 +1,74 @@
import React, { useEffect } from "react"; import React 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 <td className="text-start cursor-pointer" style={{ width: "18%" }} colSpan={2} onClick={() => {
className="text-start cursor-pointer" if (IsActive) {
style={{ width: "18%" }} setIsOpenModalNote(true);
colSpan={2} setOpen_contact(contact);
onClick={() => { }
if (IsActive) { }}>
setIsOpenModalNote(true); <div className="d-flex align-items-center">
setOpen_contact(contact); <Avatar
} size="xs"
}} classAvatar="m-0"
> firstName={(contact?.name || "").trim().split(" ")[0]?.charAt(0) || ""}
<div className="d-flex align-items-center"> lastName={(contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""}
<Avatar />
size="xs" <span className="text-truncate mx-0" style={{ maxWidth: "150px" }}>{contact?.name || ""}</span>
classAvatar="m-0" </div>
firstName={ </td>
(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%" }}> <td className="px-2" style={{ width: "20%" }}>
<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 <i className={getEmailIcon(email.label)} style={{ fontSize: "12px" }}></i>
className={getEmailIcon(email.label)} <a href={`mailto:${email.emailAddress}`} className="text-decoration-none ms-1">{email.emailAddress}</a>
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> </span>
</td> ))}
</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"
onClick={() => {
setSelectedContact(contact);
setIsOpenModal(true);
}}></i>
<i className="bx bx-trash bx-sm text-danger cursor-pointer"
onClick={() => IsDeleted(contact.id)}></i>
</td>
)}
</tr>
<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,13 +12,8 @@ import EmployeeList from "./EmployeeList";
import { useAllEmployees, useEmployees } from "../../hooks/useEmployees"; import { useAllEmployees, useEmployees } from "../../hooks/useEmployees";
import { useSortableData } from "../../hooks/useSortableData"; import { useSortableData } from "../../hooks/useSortableData";
import ConfirmModal from "../common/ConfirmModal"; 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 [bucketList, setBucketList] = useState([]);
const { employeesList } = useAllEmployees(false); const { employeesList } = useAllEmployees(false);
const [selectedEmployee, setSelectEmployee] = useState([]); const [selectedEmployee, setSelectEmployee] = useState([]);
@ -27,9 +22,7 @@ const ManageBucket = () =>
const [isSubmitting, setSubmitting] = useState(false); const [isSubmitting, setSubmitting] = useState(false);
const [selected_bucket, select_bucket] = useState(null); const [selected_bucket, select_bucket] = useState(null);
const [deleteBucket, setDeleteBucket] = 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 { const {
items: sortedBuckteList, items: sortedBuckteList,
requestSort, requestSort,
@ -343,25 +336,19 @@ const ManageBucket = () =>
</td> </td>
<td>{bucket.numberOfContacts}</td> <td>{bucket.numberOfContacts}</td>
<td className="justify-content-center"> <td className="justify-content-center">
<div className="d-flex justify-content-center align-items-center gap-2">
{(DirManager || <i
DirAdmin || className="bx bx-edit bx-sm text-primary cursor-pointer"
bucket?.createdBy?.id === onClick={() => {
profile?.employeeInfo?.id) && ( select_bucket(bucket);
<div className="d-flex justify-content-center align-items-center gap-2"> setAction_bucket(true);
<i
className="bx bx-edit bx-sm text-primary cursor-pointer "
onClick={() => {
select_bucket(bucket);
setAction_bucket(true);
}} }}
></i> ></i>
<i <i
className="bx bx-trash bx-sm text-danger cursor-pointer" className="bx bx-trash bx-sm text-danger cursor-pointer"
onClick={() => setDeleteBucket(bucket?.id)} onClick={() => setDeleteBucket(bucket?.id)}
></i> ></i>
</div> </div>
)}
</td> </td>
</tr> </tr>
))} ))}
@ -399,7 +386,7 @@ const ManageBucket = () =>
<EmployeeList <EmployeeList
employees={employeesList} employees={employeesList}
onChange={(data) => setSelectEmployee(data)} onChange={(data) => setSelectEmployee(data)}
bucket={selected_bucket} assignedEmployee={selected_bucket?.employeeIds}
/> />
)} )}

View File

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

View File

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

View File

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

View File

@ -5,28 +5,12 @@ import moment from "moment";
import NotesDirectory from "./NotesDirectory"; import NotesDirectory from "./NotesDirectory";
const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => { const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
const { contactProfile, loading, refetch } = useContactProfile(contact?.id); const { conatProfile, loading } = useContactProfile(contact?.id);
const [copiedIndex, setCopiedIndex] = useState(null);
const [profileContact, setProfileContact] = useState(); 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(() => { useEffect(() => {
setProfileContact(contactProfile); setProfileContact(conatProfile);
}, [contactProfile]); }, [conatProfile]);
const handleCopy = (email, index) => {
navigator.clipboard.writeText(email);
setCopiedIndex(index);
setTimeout(() => setCopiedIndex(null), 2000); // Reset after 2 seconds
};
return ( return (
<div className="p-1"> <div className="p-1">
<div className="text-center m-0 p-0"> <div className="text-center m-0 p-0">
@ -36,7 +20,6 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
<div className="d-flex align-items-center mb-2"> <div className="d-flex align-items-center mb-2">
<Avatar <Avatar
size="sm" size="sm"
classAvatar="m-0"
firstName={ firstName={
(contact?.name || "").trim().split(" ")[0]?.charAt(0) || "" (contact?.name || "").trim().split(" ")[0]?.charAt(0) || ""
} }
@ -44,187 +27,92 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
(contact?.name || "").trim().split(" ")[1]?.charAt(0) || "" (contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""
} }
/> />
<div className="d-flex flex-column text-start ms-1"> <div className="d-flex flex-column text-start ms-2">
<span className="m-0 fw-semibold">{contact?.name}</span> <span className="m-0 fw-semibold">{contact?.name}</span>
<small className="text-secondary small-text"> <span className="small">
{contactProfile?.tags?.map((tag) => tag.name).join(" | ")} <i className="bx bx-building bx-xs"></i>{" "}
</small> {conatProfile?.organization}
</span>
<span className="small-text">Manager</span>
</div> </div>
</div> </div>
<div className="row"> <div className="row">
<div className="col-12 col-md-6 d-flex flex-column text-start"> <div className="col-12 col-md-6 d-flex flex-column text-start">
{contactProfile?.contactEmails?.length > 0 && ( {conatProfile?.contactEmails?.length > 0 && (
<div className="d-flex mb-2"> <div className="d-flex mb-2">
<div style={{ width: "100px", minWidth: "100px" }}> <div style={{ width: "100px", minWidth: "100px" }}>
<p className="m-0">Email:</p> <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>
)} <div>
<ul className="list-inline mb-0">
{contactProfile?.contactPhones?.length > 0 && ( {conatProfile.contactEmails.map((email, idx) => (
<div className="d-flex mb-2"> <li className="list-inline-item me-3" key={idx}>
<div style={{ width: "100px", minWidth: "100px" }}> <i className="bx bx-envelope bx-xs me-1"></i>
<p className="m-0">Phone : </p> {email.emailAddress}
</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>
)}
{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> </li>
</ul> ))}
</div> </ul>
</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>
<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>
<div className="text-start"> )}
<ul className="list-inline mb-0"> {conatProfile?.contactPhones?.length > 0 && (
{contactProfile.projects.map((project, index) => ( <div className="d-flex mb-2">
<li className="list-inline-item me-2" key={project.id}> <div style={{ width: "100px", minWidth: "100px" }}>
{project.name} <p className="m-0">Phone</p>
{index < contactProfile.projects.length - 1 && ","} </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>
</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> </li>
))} </ul>
</ul> </div>
</div> </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="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" /> <hr className="my-1" />
<NotesDirectory <NotesDirectory
refetchProfile={refetch}
isLoading={loading} isLoading={loading}
contactProfile={profileContact} contactProfile={profileContact}
setProfileContact={setProfileContact} setProfileContact={setProfileContact}

View File

@ -387,10 +387,13 @@ await submitContact({ ...cleaned, id: existingContact.id });
)} )}
</div> </div>
<div className="row"> <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> <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>} {bucketsLoaging && <p>Loading...</p>}
{buckets?.map((item) => ( {buckets?.map((item) => (
<li <li
@ -414,12 +417,11 @@ await submitContact({ ...cleaned, id: existingContact.id });
</div> </div>
</li> </li>
))} ))}
{errors.bucketIds && (
<small className="danger-text mt-0">
{errors.bucketIds.message}
</small>
)}
</ul> </ul>
{errors.BucketIds && (
<small className="text-danger">{errors.BucketIds.message}</small>
)}
</div> </div>
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,7 +19,6 @@ 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 = () =>
{ {
@ -34,15 +33,12 @@ 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, setDeleting ] = useState( false ); const [ IsDeleting, setIsDeletng ] = 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 } =
@ -82,43 +78,28 @@ const Directory = () =>
} }
}; };
const handleDeleteContact = async (overrideId = null) => { const handleDeleteContact = async () => {
try try {
{ setIsDeletng(true);
if (!IsActive) { const contacts_cache = getCachedData("contacts")?.data || [];
setDirActions((prev) => ({ ...prev, action: true }));
} else { const response = await DirectoryRepository.DeleteContact(deleteContact);
setDeleting(true); 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 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 = () => { const closedModel = () => {
setIsOpenModal(false); setIsOpenModal(false);
@ -325,6 +306,7 @@ const handleDeleteContact = async (overrideId = null) => {
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>}
@ -332,7 +314,7 @@ const handleDeleteContact = async (overrideId = null) => {
<p>No Matching Contact Found</p> <p>No Matching Contact Found</p>
)} )}
{listView ? ( {listView ? (
<DirectoryListTableHeader> <DirectoryListTableHeader IsActive={IsActive}>
{loading && ( {loading && (
<tr> <tr>
<td colSpan={10}>Loading...</td> <td colSpan={10}>Loading...</td>
@ -354,7 +336,6 @@ const handleDeleteContact = async (overrideId = null) => {
setOpen_contact={setOpen_contact} setOpen_contact={setOpen_contact}
setIsOpenModalNote={setIsOpenModalNote} setIsOpenModalNote={setIsOpenModalNote}
IsDeleted={setDeleteContact} IsDeleted={setDeleteContact}
restore={handleDeleteContact}
/> />
))} ))}
</DirectoryListTableHeader> </DirectoryListTableHeader>
@ -373,16 +354,15 @@ const handleDeleteContact = async (overrideId = null) => {
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 ">
<ul className="pagination pagination-sm justify-content-end py-1"> <ul className="pagination pagination-sm justify-content-end py-1">
<li <li
@ -426,7 +406,7 @@ const handleDeleteContact = async (overrideId = null) => {
</nav> </nav>
)} )}
</div> </div>
</div> </div>
); );
}; };

View File

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

View File

@ -16,6 +16,7 @@ const DirectoryPageHeader = ({
applyFilter, applyFilter,
loading, loading,
IsActive, IsActive,
setIsOpenModal,
setOpenBucketModal, setOpenBucketModal,
}) => { }) => {
const [filtered, setFiltered] = useState(); const [filtered, setFiltered] = useState();
@ -98,7 +99,7 @@ const DirectoryPageHeader = ({
<div className="d-flex flex-wrap"> <div className="d-flex flex-wrap">
{filteredBuckets.map(({ id, name }) => ( {filteredBuckets.map(({ id, name }) => (
<div <div
className="form-check me-3 mb-1" className="form-check me-1 mb-1"
style={{ minWidth: "33.33%" }} style={{ minWidth: "33.33%" }}
key={id} key={id}
> >
@ -126,7 +127,7 @@ const DirectoryPageHeader = ({
<div className="d-flex flex-wrap"> <div className="d-flex flex-wrap">
{filteredCategories.map(({ id, name }) => ( {filteredCategories.map(({ id, name }) => (
<div <div
className="form-check me-3 mb-1" className="form-check me-1 mb-1"
style={{ minWidth: "33.33%" }} style={{ minWidth: "33.33%" }}
key={id} key={id}
> >
@ -168,10 +169,38 @@ const DirectoryPageHeader = ({
</div> </div>
</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"> <div className="col-12 col-md-8 mb-2 px-1 d-flex justify-content-end gap-2 align-items-center text-end">
<label className="switch switch-primary align-self-start mb-2"> <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">
<input <input
type="checkbox" type="checkbox"
className="switch-input me-3" className="switch-input"
onChange={() => setIsActive(!IsActive)} onChange={() => setIsActive(!IsActive)}
value={IsActive} value={IsActive}
disabled={loading} disabled={loading}
@ -180,10 +209,23 @@ const DirectoryPageHeader = ({
<span className="switch-on"></span> <span className="switch-on"></span>
<span className="switch-off"></span> <span className="switch-off"></span>
</span> </span>
<span className=" list-inline-item ms-12 small-text"> <span className=" list-inline-item ">
Show Inactive Contacts Show Inactive Contacts
</span> </span>
</label> </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>
</div> </div>
</> </>

View File

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