Merge pull request 'pramod_Task-#357 : Added Admin Update and Delete Functionality in Manage Buckets Modal' (#155) from pramod_Task-#357 into Feature_Directory

Reviewed-on: #155
This commit is contained in:
pramod.mahajan 2025-05-28 07:03:52 +00:00
commit 16301def0c
21 changed files with 983 additions and 394 deletions

View File

@ -5069,6 +5069,9 @@ fieldset:disabled .btn {
.card-group > .card {
margin-bottom: var(--bs-card-group-margin);
}
.card-minHeight{
min-height: 430px;
}
@media (min-width: 576px) {
.card-group {
display: flex;

View File

@ -0,0 +1,15 @@
import React, { createContext, useContext, useState } from "react";
const FabContext = createContext();
export const FabProvider = ({ children }) => {
const [actions, setActions] = useState([]);
return (
<FabContext.Provider value={{ actions, setActions }}>
{children}
</FabContext.Provider>
);
};
export const useFab = () => useContext(FabContext);

View File

@ -30,7 +30,7 @@ const CardViewDirectory = ({
(contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""
}
/>{" "}
<p className="m-0">{contact.name}</p>
<span className="text-heading fs-6"> {contact.name}</span>
</div>
<div>
<div className={`dropdown z-2 ${!IsActive && "d-none"}`}>
@ -55,20 +55,19 @@ const CardViewDirectory = ({
setSelectedContact(contact);
setIsOpenModal(true);
}}
>
<a className="dropdown-item px-2 py-0">
<i className="bx bx-pencil bx-xs me-2"></i>
<span className="align-left small-text">Modify</span>
<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 py-0"
className="dropdown-item px-2 cursor-pointer py-1"
onClick={() => IsDeleted(contact.id)}
>
<i className="bx bx-trash bx-xs me-2"></i>
<span className="align-left small-text">Delete</span>
<i className="bx bx-trash text-danger bx-xs me-2"></i>
<span className="align-left">Delete</span>
</a>
</li>
</ul>
@ -80,7 +79,7 @@ const CardViewDirectory = ({
<li className="list-inline-item me-1" style={{ fontSize: "10px" }}>
<i className="bx bx-building bx-xs"></i>
</li>
<li className="list-inline-item" style={{ fontSize: "10px" }}>
<li className="list-inline-item text-small">
{contact.organization}
</li>
</ul>
@ -96,52 +95,54 @@ const CardViewDirectory = ({
>
<hr className="my-0" />
{contact.contactEmails[0] && (
<ul className="list-inline my-1 ">
<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 small-text">
<li className="list-inline-item text-small">
{contact.contactEmails[0]?.emailAddress}
</li>
</ul>
)}
{contact.contactPhones[0] && (
<ul className="list-inline m-0">
<li className="list-inline-item me-2">
<ul className="list-inline m-0 ">
<li className="list-inline-item me-1">
<i
className={` ${getPhoneIcon(
contact.contactPhones[0].label
)} bx-xs`}
></i>
</li>
<li className="list-inline-item small-text">
<li className="list-inline-item text-small">
{contact.contactPhones[0]?.phoneNumber}
</li>
</ul>
)}
<ul className="list-inline m-0">
<li className="list-inline-item me-2">
<i className="bx bx-merge bx-xs"></i>
<li className="list-inline-item me-2 my-1">
<i className="fa-solid fa-tag fs-6"></i>
</li>
<li className="list-inline-item small-text">
<li className="list-inline-item text-small active">
{contact.contactCategory.name}
</li>
</ul>
<ul className="list-inline m-0">
{contact.bucketIds.map((bucketId) => (
<React.Fragment key={bucketId}>
<li className="list-inline-item me-1">
<i className="bx bx-pin bx-xs"></i>
</li>
<li className="list-inline-item small-text">
{getBucketNameById(buckets, bucketId)}
</li>
</React.Fragment>
))}
</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()), // 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()).optional(),
bucketIds: z.array(z.string()).min(1,{message:"At least one Label required"}),
})
// .refine((data) => {

View File

@ -0,0 +1,147 @@
import React, { useState } from "react";
import { useSortableData } from "../../hooks/useSortableData";
const EmployeeList = ({ employees }) => {
const [selectedIds, setSelectedIds] = useState([]);
const [searchTerm, setSearchTerm] = useState("");
const handleCheckboxChange = (id) => {
setSelectedIds((prev) =>
prev.includes(id) ? prev?.filter((empId) => empId !== id) : [...prev, id]
);
};
const getSelectedEmployees = () => {
console.log("Selected Employee IDs:", selectedIds);
};
const {
items: sortedEmployees,
requestSort,
sortConfig,
} = useSortableData(employees, {
key: (e) => `${e?.firstName} ${e?.lastName}`,
direction: "asc",
});
const getSortIcon = () => {
if (!sortConfig) return null;
return sortConfig.direction === "asc" ? (
<i className="bx bx-caret-up text-secondary"></i>
) : (
<i className="bx bx-caret-down text-secondary"></i>
);
};
const filteredEmployees = sortedEmployees?.filter((employee) => {
const fullName =
`${employee?.firstName} ${employee?.lastName}`?.toLowerCase();
// const email = employee.email.toLowerCase();
// const role = employee.jobRole.toLowerCase();
const term = searchTerm?.toLowerCase();
return fullName.includes(term);
// email.includes(term) ||
// role.includes(term)
});
return (
<>
<div className="d-flex justify-content-between mt-2">
<p className="m-0 fs-6 fw-normal">Add Employee</p>
<div className="px-1">
<input
type="search"
className="form-control form-control-sm"
placeholder="Search Employee..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
</div>
<div className="table-responsive px-1 my-1 px-sm-0">
<table className="table align-middle mb-0">
<thead className="table-light">
<tr>
<th
onClick={() =>
requestSort((e) => `${e.firstName} ${e.lastName}`)
}
className="text-start cursor-pointer"
>
<span className="ps-2">Name {getSortIcon()}</span>
</th>
<th className="text-start">Role</th>
<th scope="col">Status</th>
<th className="text-start">Bucket</th>
</tr>
</thead>
<tbody>
{employees.length === 0 ? (
<tr>
<td colSpan={4} >
<div className="d-flex justify-content-center align-items-center py-5">
No Employee Available
</div>
</td>
</tr>
) : filteredEmployees.length === 0 ? (
<tr className="my-4">
<td colSpan={4}>
<div className="d-flex justify-content-center align-items-center py-5">
No Matchinng Employee Found.
</div>
</td>
</tr>
) : (
filteredEmployees?.map((employee) => (
<tr key={employee.id}>
<td>
<div className="d-flex align-items-start text-start">
<input
className="form-check-input me-3 mt-1"
type="checkbox"
checked={selectedIds.includes(employee.id)}
onChange={() => handleCheckboxChange(employee.id)}
/>
<div>
<p className="fw-semibold mb-0">
{`${employee.firstName} ${employee.lastName}`}
</p>
<small className="text-muted">{employee.email}</small>
</div>
</div>
</td>
<td className="text-start">
<small className="text-muted">{employee.jobRole}</small>
</td>
<td>
<span
className={`badge rounded-pill px-3 py-1 ${
employee.isActive
? "bg-success-subtle text-success"
: "bg-danger-subtle text-danger"
}`}
>
{employee.isActive ? "Active" : "Inactive"}
</span>
</td>
<td className="text-start">
<small className="text-muted">
<i className="fa fa-hashtag" aria-hidden="true"></i>{" "}
{employee.jobRole}
</small>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</>
);
};
export default EmployeeList;

View File

@ -8,15 +8,36 @@ import Directory from "../../pages/Directory/Directory";
import { DirectoryRepository } from "../../repositories/DirectoryRepository";
import { cacheData, getCachedData } from "../../slices/apiDataManager";
import { useBuckets } from "../../hooks/useDirectory";
import EmployeeList from "./EmployeeList";
import { useAllEmployees, useEmployees } from "../../hooks/useEmployees";
import { useSortableData } from "../../hooks/useSortableData";
import ConfirmModal from "../common/ConfirmModal";
const ManageBucket = () =>
{
const ManageBucket = () => {
const [bucketList, setBucketList] = useState([]);
const { buckets } = useBuckets();
const { employeesList } = useAllEmployees(false);
const { buckets, loading,refetch } = useBuckets();
const [action_bucket, setAction_bucket] = useState(false);
const [isSubmitting, setSubmitting] = useState(false);
const [selected_bucket, select_bucket] = useState(null);
const [deleteBucket, setDeleteBucket] = useState(null);
const [searchTerm, setSearchTerm] = useState("");
const {
items: sortedBuckteList,
requestSort,
sortConfig,
} = useSortableData(bucketList, {
key: (e) => `${e.name}`,
direction: "asc",
});
const getSortIcon = () => {
if (!sortConfig) return null;
return sortConfig.direction === "asc" ? (
<i className="bx bx-caret-up text-secondary"></i>
) : (
<i className="bx bx-caret-down text-secondary"></i>
);
};
const {
register,
@ -29,197 +50,290 @@ const ManageBucket = () =>
name: "",
description: "",
},
} );
const onSubmit = async (data) => {
setSubmitting(true);
try {
let response;
});
if ( selected_bucket )
{
let payload ={...data, id:selected_bucket.id}
response = await DirectoryRepository.UpdateBuckets(selected_bucket.id, payload);
const cache_buckets = getCachedData("buckets") || [];
const updatedBuckets = cache_buckets.map((bucket) =>
bucket.id === selected_bucket.id ? response?.data : bucket
);
cacheData( "buckets", updatedBuckets );
setBucketList(updatedBuckets);
showToast("Bucket Updated Successfully", "success");
} else {
response = await DirectoryRepository.CreateBuckets(data);
const cache_buckets = getCachedData("buckets") || [];
const updatedBuckets = [...cache_buckets, response?.data];
cacheData( "buckets", updatedBuckets );
setBucketList(updatedBuckets);
showToast("Bucket Created Successfully", "success");
const onSubmit = async (data) => {
setSubmitting(true);
try {
let response;
if (selected_bucket) {
let payload = { ...data, id: selected_bucket.id };
response = await DirectoryRepository.UpdateBuckets(
selected_bucket.id,
payload
);
const cache_buckets = getCachedData("buckets") || [];
const updatedBuckets = cache_buckets.map((bucket) =>
bucket.id === selected_bucket.id ? response?.data : bucket
);
cacheData("buckets", updatedBuckets);
setBucketList(updatedBuckets);
showToast("Bucket Updated Successfully", "success");
} else {
response = await DirectoryRepository.CreateBuckets(data);
const cache_buckets = getCachedData("buckets") || [];
const updatedBuckets = [...cache_buckets, response?.data];
cacheData("buckets", updatedBuckets);
setBucketList(updatedBuckets);
showToast("Bucket Created Successfully", "success");
}
handleBack();
} catch (error) {
const message =
error?.response?.data?.message ||
error?.message ||
"Error occurred during API call";
showToast(message, "error");
}
};
handleBack();
} catch (error) {
const message =
error?.response?.data?.message ||
error?.message ||
"Error occurred during API call";
showToast(message, "error");
}
};
const handleDeleteContact = async () => {
try {
const resp = await DirectoryRepository.DeleteBucket( deleteBucket );
const cache_buckets = getCachedData("buckets") || [];
const updatedBuckets = cache_buckets.filter((bucket) =>
bucket.id != deleteBucket
);
cacheData("buckets", updatedBuckets);
setBucketList(updatedBuckets);
showToast("Bucket deleted successfully", "success");
setDeleteBucket(null);
} catch (error) {
const message =
error?.response?.data?.message ||
error?.message ||
"Error occurred during API call.";
showToast(message, "error");
}
};
useEffect(() => {
reset({
name: selected_bucket?.name || "",
description: selected_bucket?.description || "",
});
}, [ selected_bucket ] );
}, [selected_bucket]);
useEffect(() => {
setBucketList(buckets);
}, [buckets]);
useEffect( () =>
{
setBucketList( buckets )
}, [ buckets ] )
const handleBack = () => {
select_bucket(null);
setAction_bucket(false);
setSubmitting(false);
};
const sortedBucktesList = sortedBuckteList?.filter((bucket) => {
const term = searchTerm?.toLowerCase();
const name = bucket.name?.toLowerCase();
return name?.includes(term);
});
return (
<div className="container m-0 p-0" style={{ minHeight: "200px" }}>
<div className="d-flex justify-content-center">
<p className="fs-h6 fw-semibold m-0">Manage Buckets</p>
</div>
<div className="d-flex justify-content-between px-2 px-sm-0 mt-5 mt-sm-1 align-items-center">
<i
className={`fa-solid fa-arrow-left fs-5 cursor-pointer ${
action_bucket ? "" : "d-none"
}`}
onClick={handleBack}
></i>
<button
type="button"
className={`btn btn-xs btn-primary ms-auto ${
action_bucket ? "d-none" : ""
}`}
onClick={() => setAction_bucket(true)}
<>
{deleteBucket && (
<div
className={`modal fade ${deleteBucket ? "show" : ""}`}
tabIndex="-1"
role="dialog"
style={{
display: deleteBucket ? "block" : "none",
backgroundColor: deleteBucket ? "rgba(0,0,0,0.5)" : "transparent",
}}
>
<i className="bx bx-plus-circle me-2"></i>
Add Bucket
</button>
</div>
<div>
{!action_bucket ? (
<div className="table-responsive text-nowrap pt-1 px-2 px-sm-0">
<table className="table px-2">
<thead className="p-0">
<tr className="p-0">
<th colSpan={2}>
<div className="d-flex justify-content-start align-items-center gap-1">
<IconButton
size={12}
iconClass="fa-solid fa-bucket"
color="info"
/>
<span>Name</span>
</div>
</th>
<th className="text-start d-none d-sm-table-cell">
<div className="d-flex align-items-center justify-content-start gap-1">
<IconButton
size={12}
iconClass="fa-solid fa-file-lines"
color="primary"
/>
<span>Description</span>
</div>
</th>
<th>
<div className="d-flex align-items-center justify-content-center gap-1">
<IconButton
size={12}
iconClass="fa-solid fa-gear"
color="secondary"
/>
<span>Action</span>
</div>
</th>
</tr>
</thead>
<ConfirmModal
type={"delete"}
header={"Delete Bucket"}
message={"Are you sure you want delete?"}
onSubmit={handleDeleteContact}
onClose={() => setDeleteBucket(null)}
// loading={IsDeleting}
/>
</div>
)}
<tbody className="table-border-bottom-0 overflow-auto">
{bucketList.map((bucket) => (
<tr key={bucket.id}>
<td colSpan={2} className="text-start">
{bucket.name}
</td>
<td className="text-start d-none d-sm-table-cell">
{bucket.description}
</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);
}}
></i>
<i className="bx bx-trash bx-sm text-danger cursor-pointer"></i>
<i className="bx bx-user-plus cursor-pointer"></i>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<form onSubmit={handleSubmit(onSubmit)} className="px-2 px-sm-0">
<div className="">
<label className="form-label">Bucket Name</label>
<div className="container m-0 p-0" style={{ minHeight: "200px" }}>
<div className="d-flex justify-content-center">
<p className="fs-6 fw-semibold m-0">Manage Buckets</p>
</div>
<div className="d-flex justify-content-between px-2 px-sm-0 mt-5 mt-sm-1 align-items-center">
{action_bucket ? (
<i
className={`fa-solid fa-arrow-left fs-5 cursor-pointer`}
onClick={handleBack}
></i>
) : (
<div className="d-flex align-items-center gap-2">
<input
type="search"
className="form-control form-control-sm"
{...register("name")}
/>
{errors.name && (
<small className="danger-text">{errors.name.message}</small>
)}
placeholder="Search Bucket ..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<i
className={`bx bx-refresh cursor-pointer fs-4 ${loading ? "spin" : ""
}`}
title="Refresh"
onClick={() => rrefetch()}
/>
</div>
<div className="">
<label className="form-label">Bucket Discription</label>
<textarea
className="form-control form-control-sm"
{...register("description")}
/>
{errors.description && (
<small className="danger-text">
{errors.description.message}
</small>
)}
)}
<button
type="button"
className={`btn btn-sm btn-primary ms-auto ${
action_bucket ? "d-none" : ""
}`}
onClick={() => setAction_bucket(true)}
>
<i className="bx bx-plus-circle me-2"></i>
Add Bucket
</button>
</div>
<div>
{!action_bucket ? (
<div className="table-responsive text-nowrap pt-1 px-2 px-sm-0">
<table className="table px-2">
<thead className="p-0 table-light">
<tr className="p-0">
<th
colSpan={2}
className="cursor-pointer"
onClick={() => requestSort((e) => `${e.name} `)}
>
<div className="d-flex justify-content-start align-items-center gap-1 mx-2">
<span>Name {getSortIcon()}</span>
</div>
</th>
<th className="text-start d-none d-sm-table-cell">
<div className="d-flex align-items-center justify-content-center gap-1">
<span>Description</span>
</div>
</th>
<th>
<div className="d-flex align-items-center justify-content-center gap-1">
<span>Action</span>
</div>
</th>
</tr>
</thead>
<tbody className="table-border-bottom-0 overflow-auto">
{loading && (
<tr className="mt-10">
<td colSpan={4}>
{" "}
<div className="d-flex justify-content-center align-items-center py-5">
Loading...
</div>
</td>
</tr>
)}
{!loading && buckets.length == 0 && (
<tr>
<td colSpan={4}>
<div className="d-flex justify-content-center align-items-center py-5">
Bucket Not Available.
</div>
</td>
</tr>
)}
{!loading && sortedBucktesList.length == 0 && (
<tr>
<td className="text-center py-4 h-25" colSpan={4}>
<div className="d-flex justify-content-center align-items-center py-5">
No Matching Bucket Found.
</div>
</td>
</tr>
)}
{sortedBucktesList.map((bucket) => (
<tr key={bucket.id}>
<td colSpan={2} className="text-start text-wrap">
<i className="bx bx-right-arrow-alt me-1"></i>{" "}
{bucket.name}
</td>
<td
className="text-start d-none d-sm-table-cell text-wrap"
style={{ width: "60%" }}
>
{bucket.description}
</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);
}}
></i>
<i
className="bx bx-trash bx-sm text-danger cursor-pointer"
onClick={() => setDeleteBucket(bucket?.id)}
></i>
<i className="bx bx-user-plus cursor-pointer"></i>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="mt-2 d-flex justify-content-center gap-3">
<button
type="reset"
className="btn btn-sm btn-secondary"
disabled={isSubmitting}
>
Cancel
</button>
<button
type="submit"
className="btn btn-sm btn-primary"
disabled={isSubmitting}
>
{isSubmitting ? "Please wait..." : "Submit"}
</button>
</div>
</form>
)}
) : (
<>
<form onSubmit={handleSubmit(onSubmit)} className="px-2 px-sm-0">
<div className="">
<label className="form-label">Bucket Name</label>
<input
className="form-control form-control-sm"
{...register("name")}
/>
{errors.name && (
<small className="danger-text">{errors.name.message}</small>
)}
</div>
<div className="">
<label className="form-label">Bucket Discription</label>
<textarea
className="form-control form-control-sm"
rows="3"
{...register("description")}
/>
{errors.description && (
<small className="danger-text">
{errors.description.message}
</small>
)}
</div>
<div className="mt-2 d-flex justify-content-center gap-3">
<button
type="reset"
className="btn btn-sm btn-secondary"
disabled={isSubmitting}
>
Cancel
</button>
<button
type="submit"
className="btn btn-sm btn-primary"
disabled={isSubmitting}
>
{isSubmitting ? "Please wait..." : "Submit"}
</button>
</div>
</form>
<EmployeeList employees={employeesList} />
</>
)}
</div>
</div>
</div>
</>
);
};

View File

@ -14,10 +14,11 @@ import useMaster, {
} from "../../hooks/masterHook/useMaster";
import { useDispatch, useSelector } from "react-redux";
import { changeMaster } from "../../slices/localVariablesSlice";
import { useBuckets } from "../../hooks/useDirectory";
import { useBuckets, useOrganization } from "../../hooks/useDirectory";
import { useProjects } from "../../hooks/useProjects";
import SelectMultiple from "../common/SelectMultiple";
import {ContactSchema} from "./DirectorySchema";
import InputSuggestions from "../common/InputSuggestion";
@ -25,13 +26,15 @@ 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 { contactTags, loading: Tagloading } = useContactTags();
const [IsSubmitting, setSubmitting] = useState(false);
const dispatch = useDispatch();
@ -136,6 +139,7 @@ useEffect(() => {
setSubmitting(true);
submitContact(cleaned, reset, setSubmitting);
};
const orgValue = watch("organization")
const handleClosed = () => {
onCLosed();
@ -160,10 +164,12 @@ useEffect(() => {
<div className="col-md-6 text-start">
<label className="form-label">Organization</label>
<input
className="form-control form-control-sm"
{...register("organization")}
/>
<InputSuggestions
organizationList={organizationList}
value={getValues("organization") || ""}
onChange={(val) => setValue("organization", val)}
error={errors.organization?.message}
/>
{errors.organization && (
<small className="danger-text">
{errors.organization.message}
@ -356,8 +362,7 @@ useEffect(() => {
<label className="form-label ">Select Label</label>
<ul
className="d-flex flex-wrap px-1 list-unstyled overflow-auto mb-0"
style={{ maxHeight: "80px" }}
className="d-flex flex-wrap px-1 list-unstyled mb-0"
>
{bucketsLoaging && <p>Loading...</p>}
{buckets?.map((item) => (

View File

@ -6,7 +6,6 @@ import NotesDirectory from "./NotesDirectory";
const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
const { conatProfile, loading } = useContactProfile(contact?.id);
const [activeTab, setActiveTab] = useState("profile");
const [profileContact, setProfileContact] = useState();
useEffect(() => {
@ -34,9 +33,11 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
<i className="bx bx-building bx-xs"></i>{" "}
{conatProfile?.organization}
</span>
<span className="small-text">Manager</span>
</div>
</div>
<div className="d-flex flex-column text-start">
<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" }}>
@ -61,7 +62,7 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
</div>
<div>
<ul className="list-inline mb-0">
{conatProfile.contactPhones.map((phone, idx) => (
{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}
@ -87,7 +88,28 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
</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>
<hr className="my-1" />
<NotesDirectory

View File

@ -386,8 +386,8 @@ await submitContact({ ...cleaned, id: existingContact.id });
<label className="form-label ">Select Label</label>
<ul
className="d-flex flex-wrap px-1 list-unstyled overflow-auto mb-0"
style={{ maxHeight: "80px" }}
className="d-flex flex-wrap px-1 list-unstyled mb-0"
>
{bucketsLoaging && <p>Loading...</p>}
{buckets?.map((item) => (

View File

@ -0,0 +1,35 @@
.fab-container {
position: fixed;
bottom: 35px;
right: 30px;
z-index: 1050;
display: flex;
flex-direction: column;
align-items: end;
}
.fab-main {
/* width: 45px;
height: 45px;
border-radius: 100%;
background-color: #0d6efd;
color: white;
border: none; */
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
/* font-size: 24px; */
cursor: pointer;
pointer-events: auto;
}
.fab-option {
pointer-events: auto;
margin-bottom: 10px;
}
@media (max-width: 768px) {
.fab-container {
right: 20px;
left: 50%;
bottom: 20px;
}
}

View File

@ -0,0 +1,33 @@
import React, { useState } from "react";
import {useFab} from "../../Context/FabContext";
import './FloatingMenu.css'
const FloatingMenu = () => {
const { actions } = useFab();
const [open, setOpen] = useState(false);
if (actions.length === 0) return null;
return (
<div className="fab-container">
{open &&
actions.map((action, index) => (
<button
key={index}
className={`badge bg-label-${action.color} rounded-pill mb-2 d-inline-flex align-items-center gap-2 px-3 py-1 cursor-pointer fab-option`}
onClick={action.onClick}
title={action.label}
>
<i className={action.icon}></i>
<span>{action.label}</span>
</button>
))}
<button type="button" className="btn btn-lg btn-icon rounded-pill me-2 btn-primary fab-main " onClick={() => setOpen(!open)}>
<span className={`bx ${open ? "bx-x" : "bx-plus"}`}></span>
</button>
</div>
);
};
export default FloatingMenu;

View File

@ -54,7 +54,20 @@ useEffect(() => {
// Dynamically generate data-bs attributes
const dataAttributesProps = Object.keys(dataAttributes).map(key => ({
[key]: dataAttributes[key],
}));
} ) );
// The gray background
const backdropStyle = {
position: 'fixed',
top: 0,
bottom: 0,
left: 0,
right: 0,
backgroundColor: 'rgba(0,0,0,0.3)',
};
return (
<div
@ -65,8 +78,9 @@ useEffect(() => {
aria-hidden="true"
ref={modalRef} // Assign the ref to the modal element
{...dataAttributesProps}
style={backdropStyle}
>
<div className={`modal-dialog ${dialogClass} ${modalSizeClass } mx-sm-auto mx-1`} role={role}>
<div className={`modal-dialog ${dialogClass} ${modalSizeClass } mx-sm-auto mx-1`} role={role} >
<div className="modal-content">
<div className="modal-header p-0">
{/* Close button inside the modal header */}

View File

@ -0,0 +1,79 @@
import React, { useState } from "react";
const InputSuggestions = ({
organizationList = [],
value,
onChange,
error,
}) => {
const [filteredList, setFilteredList] = useState([]);
const [showSuggestions, setShowSuggestions] = useState(false);
const handleInputChange = (e) => {
const val = e.target.value;
onChange(val);
const matches = organizationList.filter((org) =>
org.toLowerCase().includes(val.toLowerCase())
);
setFilteredList(matches);
setShowSuggestions(true);
};
const handleSelectSuggestion = (val) => {
onChange(val);
setShowSuggestions(false);
};
return (
<div className="position-relative">
<input
className="form-control form-control-sm"
value={value}
onChange={handleInputChange}
onBlur={() => setTimeout(() => setShowSuggestions(false), 150)}
onFocus={() => {
if (value) setShowSuggestions(true);
}}
/>
{showSuggestions && filteredList.length > 0 && (
<ul
className="list-group shadow-sm position-absolute w-100 bg-white border zindex-tooltip"
style={{
maxHeight: "180px",
overflowY: "auto",
marginTop: "2px",
zIndex: 1000,
borderRadius:"0px"
}}
>
{filteredList.map((org) => (
<li
key={org}
className="list-group-item list-group-item-action border-none "
style={{
cursor: "pointer",
padding: "5px 12px",
fontSize: "14px",
transition: "background-color 0.2s",
}}
onMouseDown={() => handleSelectSuggestion(org)}
onMouseEnter={(e) =>
(e.currentTarget.style.backgroundColor = "#f8f9fa")
}
onMouseLeave={(e) =>
(e.currentTarget.style.backgroundColor = "transparent")
}
>
{org}
</li>
))}
</ul>
)}
{error && <small className="danger-text">{error}</small>}
</div>
);
};
export default InputSuggestions;

View File

@ -1,21 +1,21 @@
import React, { useState, useEffect, useRef } from 'react';
import { useFormContext } from 'react-hook-form';
import './MultiSelectDropdown.css';
import React, { useState, useEffect, useRef } from "react";
import { useFormContext } from "react-hook-form";
import "./MultiSelectDropdown.css";
const SelectMultiple = ({
name,
options = [],
label = 'Select options',
labelKey = 'name',
valueKey = 'id',
placeholder = 'Please select...',
IsLoading = false
label = "Select options",
labelKey = "name",
valueKey = "id",
placeholder = "Please select...",
IsLoading = false,
}) => {
const { setValue, watch } = useFormContext();
const selectedValues = watch(name) || [];
const [isOpen, setIsOpen] = useState(false);
const [searchText, setSearchText] = useState('');
const [searchText, setSearchText] = useState("");
const dropdownRef = useRef(null);
useEffect(() => {
@ -24,8 +24,8 @@ const SelectMultiple = ({
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
const handleCheckboxChange = (value) => {
@ -44,35 +44,37 @@ const SelectMultiple = ({
<div ref={dropdownRef} className="multi-select-dropdown-container">
<label className="form-label mb-1">{label}</label>
<div
className="multi-select-dropdown-header"
onClick={() => setIsOpen((prev) => !prev)}
>
<span
className={
selectedValues.length > 0
? 'placeholder-style-selected'
: 'placeholder-style'
}
>
<div className="selected-badges-container">
{selectedValues.length > 0 ? (
selectedValues.map((val) => {
const found = options.find((opt) => opt[valueKey] === val);
return (
<span key={val} className="badge badge-selected-item mx-1">
{found ? found[labelKey] : ''}
</span>
);
})
) : (
<span className="placeholder-text">{placeholder}</span>
)}
</div>
</span>
<i className="bx bx-chevron-down"></i>
</div>
<div
className="multi-select-dropdown-header"
onClick={() => setIsOpen((prev) => !prev)}
>
<span
className={
selectedValues.length > 0
? "placeholder-style-selected"
: "placeholder-style"
}
>
<div className="selected-badges-container">
{selectedValues.length > 0 ? (
selectedValues.map((val) => {
const found = options.find((opt) => opt[valueKey] === val);
return (
<span
key={val}
className="badge badge-selected-item mx-1 mb-1"
>
{found ? found[labelKey] : ""}
</span>
);
})
) : (
<span className="placeholder-text">{placeholder}</span>
)}
</div>
</span>
<i className="bx bx-chevron-down"></i>
</div>
{isOpen && (
<div className="multi-select-dropdown-options">
@ -94,7 +96,9 @@ const SelectMultiple = ({
return (
<div
key={valueVal}
className={`multi-select-dropdown-option ${isChecked ? 'selected' : ''}`}
className={`multi-select-dropdown-option ${
isChecked ? "selected" : ""
}`}
>
<input
type="checkbox"
@ -105,14 +109,16 @@ const SelectMultiple = ({
<label className="text-secondary">{labelVal}</label>
</div>
);
} )}
})}
{!IsLoading && filteredOptions.length === 0 && (
<div className='multi-select-dropdown-Not-found'>
<label className="text-muted">Not Found {`'${searchText}'`}</label>
<div className="multi-select-dropdown-Not-found">
<label className="text-muted">
Not Found {`'${searchText}'`}
</label>
</div>
)}
{IsLoading && filteredOptions.length === 0 && (
<div className='multi-select-dropdown-Not-found'>
<div className="multi-select-dropdown-Not-found">
<label className="text-muted">Loading...</label>
</div>
)}

View File

@ -37,47 +37,46 @@ export const useDirectory = (isActive) => {
};
};
export const useBuckets = () => {
const [buckets, setBuckets] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const fetchBuckets = async () => {
const cacheBuckets = getCachedData("buckets");
if (!cacheBuckets) {
setLoading( true );
try {
const resp = await DirectoryRepository.GetBucktes();
setBuckets(resp.data);
cacheData( "buckets", resp.data );
setLoading(false);
} catch (error) {
const msg =
error?.response?.data?.message || error?.message || "Something went wrong";
setError(msg);
}
} else {
setBuckets(cacheBuckets);
setLoading(true);
try {
const resp = await DirectoryRepository.GetBucktes();
setBuckets(resp.data);
cacheData("buckets", resp.data);
setLoading(false);
} catch (error) {
const msg =
error?.response?.data?.message ||
error?.message ||
"Something went wrong";
setError( msg );
setLoading(false);
}
};
useEffect(() => {
fetchBuckets();
const cacheBuckets = getCachedData("buckets");
if (!cacheBuckets) {
fetchBuckets();
} else {
setBuckets(cacheBuckets);
}
}, []);
return { buckets, loading, error };
return { buckets, loading, error, refetch: fetchBuckets };
};
export const useContactProfile = (id) =>
{
const [ conatProfile, setContactProfile ] = useState( null );
const [ loading, setLoading ] = useState( false );
const [ Error, setError ] = useState( "" );
export const useContactProfile = (id) => {
const [conatProfile, setContactProfile] = useState(null);
const [loading, setLoading] = useState(false);
const [Error, setError] = useState("");
const fetchContactProfile = async () => {
const fetchContactProfile = async () => {
const cached = getCachedData("Contact Profile");
if (!cached || cached.contactId !== id) {
@ -88,7 +87,9 @@ const fetchContactProfile = async () => {
cacheData("Contact Profile", { data: resp.data, contactId: id });
} catch (err) {
const msg =
err?.response?.data?.message || err?.message || "Something went wrong";
err?.response?.data?.message ||
err?.message ||
"Something went wrong";
setError(msg);
} finally {
setLoading(false);
@ -99,35 +100,33 @@ const fetchContactProfile = async () => {
};
useEffect(() => {
if ( id )
{
if (id) {
fetchContactProfile(id);
}
}, [id]);
return { conatProfile, loading, Error };
}
};
export const useContactNotes = (id, IsActive) => {
const [contactNotes, setContactNotes] = useState([]);
const [loading, setLoading] = useState(false);
const [Error, setError] = useState("");
export const useContactNotes = (id,IsActive) =>
{
const [ contactNotes, setContactNotes ] = useState( [] );
const [ loading, setLoading ] = useState( false );
const [ Error, setError ] = useState( "" );
const fetchContactNotes = async () => {
const fetchContactNotes = async () => {
const cached = getCachedData("Contact Notes");
if (!cached || cached.contactId !== id) {
setLoading(true);
try {
const resp = await DirectoryRepository.GetNote(id,IsActive);
const resp = await DirectoryRepository.GetNote(id, IsActive);
setContactNotes(resp.data);
cacheData("Contact Notes", { data: resp.data, contactId: id });
} catch (err) {
const msg =
err?.response?.data?.message || err?.message || "Something went wrong";
err?.response?.data?.message ||
err?.message ||
"Something went wrong";
setError(msg);
} finally {
setLoading(false);
@ -138,11 +137,43 @@ const fetchContactNotes = async () => {
};
useEffect(() => {
if ( id )
{
if (id) {
fetchContactNotes(id);
}
}, [id]);
return { contactNotes, loading, Error };
}
};
export const useOrganization = () => {
const [organizationList, setOrganizationList] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const fetchOrg = async () => {
const cacheOrg = getCachedData("organizations");
if (cacheOrg?.length != 0) {
setLoading(true);
try {
const resp = await DirectoryRepository.GetOrganizations();
cacheData("organizations", resp.data);
setOrganizationList(resp.data);
setLoading(false);
} catch (error) {
const msg =
error?.response?.data?.message ||
error?.message ||
"Something went wrong";
setError(msg);
}
} else {
setOrganizationList(cacheOrg);
}
};
useEffect(() => {
fetchOrg();
}, []);
return { organizationList, loading, error };
};

View File

@ -0,0 +1,35 @@
import { useState, useMemo } from 'react';
export const useSortableData = (items, config = null) => {
const [sortConfig, setSortConfig] = useState(config);
const sortedItems = useMemo(() => {
let sortableItems = [...items];
if (sortConfig !== null) {
sortableItems.sort((a, b) => {
const aValue = sortConfig.key(a).toLowerCase();
const bValue = sortConfig.key(b).toLowerCase();
if (aValue < bValue) return sortConfig.direction === 'asc' ? -1 : 1;
if (aValue > bValue) return sortConfig.direction === 'asc' ? 1 : -1;
return 0;
});
}
return sortableItems;
}, [items, sortConfig]);
const requestSort = (keyFn) => {
let direction = 'asc';
if (
sortConfig &&
sortConfig.key.toString() === keyFn.toString() &&
sortConfig.direction === 'asc'
) {
direction = 'desc';
}
setSortConfig({ key: keyFn, direction });
};
return { items: sortedItems, requestSort, sortConfig };
};

View File

@ -4,25 +4,30 @@ import Header from "../components/Layout/Header";
import Sidebar from "../components/Layout/Sidebar";
import Footer from "../components/Layout/Footer";
import FloatingMenu from "../components/common/FloatingMenu";
import { FabProvider } from "../Context/FabContext";
const HomeLayout = () => {
useEffect(() => {
Main();
}, []);
return (
<div className="layout-wrapper layout-content-navbar" >
<div className="layout-container" >
<Sidebar />
<div className="layout-page ">
<Header />
<div className="content-wrapper" >
<Outlet />
<Footer />
<FabProvider>
<div className="layout-wrapper layout-content-navbar">
<div className="layout-container">
<Sidebar />
<div className="layout-page ">
<Header />
<div className="content-wrapper">
<Outlet />
<Footer />
</div>
</div>
<FloatingMenu />
<div className="layout-overlay layout-menu-toggle"></div>
</div>
<div className="layout-overlay layout-menu-toggle"></div>
</div>
</div>
</FabProvider>
);
};

View File

@ -18,6 +18,7 @@ import ConfirmModal from "../../components/common/ConfirmModal";
import DirectoryListTableHeader from "./DirectoryListTableHeader";
import DirectoryPageHeader from "./DirectoryPageHeader";
import ManageBucket from "../../components/Directory/ManageBucket";
import {useFab} from "../../Context/FabContext";
const Directory = () =>
{
@ -36,11 +37,12 @@ const Directory = () =>
const [openBucketModal,setOpenBucketModal] = useState(false)
const [tempSelectedBucketIds, setTempSelectedBucketIds] = useState([]);
const [tempSelectedCategoryIds, setTempSelectedCategoryIds] = useState([]);
const [ tempSelectedCategoryIds, setTempSelectedCategoryIds ] = useState( [] );
const {setActions} = useFab()
const { contacts, loading } = useDirectory(IsActive);
const { contacts, loading , refetch} = useDirectory(IsActive);
const { contactCategory, loading: contactCategoryLoading } =
useContactCategory();
useContactCategory();
const {buckets} = useBuckets();
const submitContact = async (data) => {
@ -56,15 +58,17 @@ const Directory = () =>
);
showToast("Contact updated successfully", "success");
setIsOpenModal(false);
setSelectedContact(null);
setSelectedContact( null );
} else {
response = await DirectoryRepository.CreateContact(data);
updatedContacts = [...contacts_cache, response.data];
showToast("Contact created successfully", "success");
setIsOpenModal(false);
}
cacheData("Contacts", {data:updatedContacts,isActive:IsActive});
setContactList(updatedContacts);
// cacheData("Contacts", {data:updatedContacts,isActive:IsActive});
// setContactList(updatedContacts);
refetch()
} catch (error) {
const msg =
error.response?.data?.message ||
@ -80,7 +84,7 @@ const Directory = () =>
const contacts_cache = getCachedData("contacts")?.data || [];
const response = await DirectoryRepository.DeleteContact(deleteContact);
const updatedContacts = ContactList.filter((c) => c.id !== deleteContact);
const updatedContacts = ContactList.filter( ( c ) => c.id !== deleteContact );
setContactList(updatedContacts);
cacheData("Contacts", {data:updatedContacts,isActive:IsActive});
showToast("Contact deleted successfully", "success");
@ -155,7 +159,7 @@ const Directory = () =>
return matchesSearch && matchesCategory && matchesBucket;
}).sort((a, b) => a.name.localeCompare(b.name));
}, [ContactList, searchText, selectedCategoryIds, selectedBucketIds]);
}, [ContactList, searchText, selectedCategoryIds, selectedBucketIds,selectedContact]);
const applyFilter = () => {
setSelectedBucketIds(tempSelectedBucketIds);
@ -192,8 +196,30 @@ const Directory = () =>
}
};
useEffect(() => {
setActions([
{
label: "New Contact",
icon: "bx bx-plus-circle",
color: "warning",
onClick: () => setIsOpenModal(true),
},
{
label: "Manage Bucket",
icon: "fa-solid fa-bucket fs-5 ",
color: "primary",
onClick: () => setOpenBucketModal(true),
},
]);
return () => setActions([]); // Clean up
}, []);
return (
<div className="container-xxl flex-grow-1 container-p-y">
<Breadcrumb
data={[
{ label: "Home", link: "/dashboard" },
@ -257,13 +283,13 @@ const Directory = () =>
<GlobalModel
isOpen={openBucketModal}
closeModal={() =>setOpenBucketModal(false)}
size="md"
size="lg"
>
<ManageBucket buckets={buckets} />
</GlobalModel>
)}
<div className="card p-2">
<div className="card p-2 card-minHeight">
<DirectoryPageHeader
searchText={searchText}
setSearchText={setSearchText}
@ -333,6 +359,8 @@ const Directory = () =>
))}
</div>
)}
<div>
</div>
{!loading && currentItems < ITEMS_PER_PAGE && (
<nav aria-label="Page ">

View File

@ -11,43 +11,22 @@ const DirectoryListTableHeader = ( {children, IsActive} ) =>
<tr>
<th colSpan={2}>
<div className="d-flex align-items-center gap-1">
<IconButton
size={12}
iconClass="bx bx-user"
color="secondary"
onClick={() => alert("User icon clicked")}
/>
<span>Name</span>
</div>
</th>
<th className="px-2 text-start">
<div className="d-flex text-center align-items-center gap-1 justify-content-start">
<IconButton
size={12}
iconClass="bx bx-envelope"
color="primary"
/>
<span>Email</span>
</div>
</th>
<th className="mx-2">
<div className="d-flex align-items-center m-0 p-0 gap-1">
<IconButton
size={12}
iconClass="bx bx-phone"
color="warning"
onClick={() => alert("User icon clicked")}
/>
<span>Phone</span>
</div>
</th>
<th className="mx-2">
<div className="d-flex align-items-center gap-1">
<IconButton
size={12}
iconClass="bx bxs-grid-alt"
color="info"
/>
<span>Organization</span>
</div>
</th>

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect, useState } from "react";
const DirectoryPageHeader = ({
searchText,
@ -16,14 +16,19 @@ const DirectoryPageHeader = ({
applyFilter,
loading,
IsActive,
setIsOpenModal,
setOpenBucketModal
setIsOpenModal,
setOpenBucketModal,
}) => {
const [filtered, setFiltered] = useState();
useEffect(() => {
setFiltered(
tempSelectedBucketIds?.length + tempSelectedCategoryIds?.length
);
}, [tempSelectedBucketIds, tempSelectedCategoryIds]);
return (
<>
<div className="row">
</div>
<div className="row"></div>
<div className="row mx-0 px-0 align-items-center">
<div className="col-12 col-md-4 mb-2 px-1 d-flex align-items-center ">
<input
@ -66,22 +71,31 @@ const DirectoryPageHeader = ({
<div className="dropdown" style={{ width: "fit-content" }}>
<div className="dropdown" style={{ width: "fit-content" }}>
<a
className="dropdown-toggle hide-arrow cursor-pointer d-flex align-items-center"
className="dropdown-toggle hide-arrow cursor-pointer d-flex align-items-center position-relative"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i className="fa-solid fa-filter ms-1 fs-5"></i>
<i className={`fa-solid fa-filter ms-1 fs-5 ${filtered > 0 ? 'text-primary' : 'text-muted'}`}></i>
{filtered > 0 && (
<span
className="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-warning"
style={{ fontSize: "0.4rem"}}
>
{filtered}
</span>
)}
</a>
<ul className="dropdown-menu p-3" style={{ width: "320px" }}>
<div>
<p className="small-text fw-semibold text-muted m-0">
<p className="text-small text-muted m-0">
Filter by
</p>
{/* Bucket Filter */}
<div className="mt-1">
<p className="small-text mb-1 fw-semibold">Buckets</p>
<p className="text-small mb-1 ">Buckets</p>
<div className="d-flex flex-wrap">
{filteredBuckets.map(({ id, name }) => (
<div
@ -97,7 +111,7 @@ const DirectoryPageHeader = ({
onChange={() => handleTempBucketChange(id)}
/>
<label
className="form-check-label text-nowrap small-text "
className="form-check-label text-nowrap text-small "
htmlFor={`bucket-${id}`}
>
{name}
@ -109,7 +123,7 @@ const DirectoryPageHeader = ({
<hr className="m-0" />
{/* Category Filter */}
<div className="mt-1">
<p className="small-text mb-1 fw-semibold">Categories</p>
<p className="text-small mb-1 ">Categories</p>
<div className="d-flex flex-wrap">
{filteredCategories.map(({ id, name }) => (
<div
@ -125,7 +139,7 @@ const DirectoryPageHeader = ({
onChange={() => handleTempCategoryChange(id)}
/>
<label
className="form-check-label text-nowrap small-text"
className="form-check-label text-nowrap text-small"
htmlFor={`cat-${id}`}
>
{name}
@ -154,49 +168,69 @@ const DirectoryPageHeader = ({
</div>
</div>
</div>
<div className="col-12 col-md-8 mb-2 px-1 text-md-end text-end">
<label className="switch switch-primary">
<input
type="checkbox"
className="switch-input"
onChange={() => setIsActive(!IsActive)}
value={IsActive}
disabled={loading}
/>
<span className="switch-toggle-slider">
<span className="switch-on">
{/* <i class="icon-base bx bx-check"></i> */}
</span>
<span className="switch-off">
{/* <i class="icon-base bx bx-x"></i> */}
</span>
</span>
<span className="switch-label small-text">
Show Inactive Contacts
</span>
</label>
<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-xs btn-primary"
className="btn btn-sm btn-primary"
onClick={() => setIsOpenModal(true)}
>
<i className="bx bx-plus-circle me-2"></i>
New Contact
</button>
</div>
<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
type="checkbox"
className="switch-input"
onChange={() => setIsActive(!IsActive)}
value={IsActive}
disabled={loading}
/>
<span className="switch-toggle-slider">
<span className="switch-on"></span>
<span className="switch-off"></span>
</span>
<span className=" list-inline-item ">
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 className="col-12 d-flex justify-content-end">
<button
type="button"
className="btn btn-xs btn-primary"
onClick={()=>setOpenBucketModal(true)}
>
<i className="bx bx-plus-circle me-2"></i>
Manage Buckets
</button>
</div>
</div>
</>
);
};
export default DirectoryPageHeader;

View File

@ -1,6 +1,8 @@
import {api} from "../utils/axiosClient";
export const DirectoryRepository = {
GetOrganizations:()=>api.get('/api/directory/organization'),
GetContacts: (isActive) => api.get( `/api/directory?active=${isActive}` ),
CreateContact: ( data ) => api.post( '/api/directory', data ),
UpdateContact: ( id, data ) => api.put( `/api/directory/${ id }`, data ),
@ -8,7 +10,8 @@ export const DirectoryRepository = {
GetBucktes: () => api.get( `/api/directory/buckets` ),
CreateBuckets: ( data ) => api.post( `/api/Directory/bucket`, data ),
UpdateBuckets: (id,data) => api.put( `/api/Directory/bucket/${id}`,data ),
UpdateBuckets: ( id, data ) => api.put( `/api/Directory/bucket/${ id }`, data ),
DeleteBucket:(id)=>api.delete(`/api/directory/bucket/${id}`),
GetContactProfile: ( id ) => api.get( `/api/directory/profile/${ id }` ),