pramod_Task-#357 : Added Admin Update and Delete Functionality in Manage Buckets Modal #155
3
public/assets/vendor/css/core.css
vendored
3
public/assets/vendor/css/core.css
vendored
@ -5069,6 +5069,9 @@ fieldset:disabled .btn {
|
|||||||
.card-group > .card {
|
.card-group > .card {
|
||||||
margin-bottom: var(--bs-card-group-margin);
|
margin-bottom: var(--bs-card-group-margin);
|
||||||
}
|
}
|
||||||
|
.card-minHeight{
|
||||||
|
min-height: 430px;
|
||||||
|
}
|
||||||
@media (min-width: 576px) {
|
@media (min-width: 576px) {
|
||||||
.card-group {
|
.card-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
15
src/Context/FabContext.jsx
Normal file
15
src/Context/FabContext.jsx
Normal 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);
|
||||||
@ -30,7 +30,7 @@ const CardViewDirectory = ({
|
|||||||
(contact?.name || "").trim().split(" ")[1]?.charAt(0) || ""
|
(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>
|
<div>
|
||||||
<div className={`dropdown z-2 ${!IsActive && "d-none"}`}>
|
<div className={`dropdown z-2 ${!IsActive && "d-none"}`}>
|
||||||
@ -55,20 +55,19 @@ const CardViewDirectory = ({
|
|||||||
setSelectedContact(contact);
|
setSelectedContact(contact);
|
||||||
setIsOpenModal(true);
|
setIsOpenModal(true);
|
||||||
}}
|
}}
|
||||||
|
|
||||||
>
|
>
|
||||||
<a className="dropdown-item px-2 py-0">
|
<a className="dropdown-item px-2 cursor-pointer py-1">
|
||||||
<i className="bx bx-pencil bx-xs me-2"></i>
|
<i className="bx bx-edit bx-xs text-primary me-2"></i>
|
||||||
<span className="align-left small-text">Modify</span>
|
<span className="align-left ">Modify</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
className="dropdown-item px-2 py-0"
|
className="dropdown-item px-2 cursor-pointer py-1"
|
||||||
onClick={() => IsDeleted(contact.id)}
|
onClick={() => IsDeleted(contact.id)}
|
||||||
>
|
>
|
||||||
<i className="bx bx-trash bx-xs me-2"></i>
|
<i className="bx bx-trash text-danger bx-xs me-2"></i>
|
||||||
<span className="align-left small-text">Delete</span>
|
<span className="align-left">Delete</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -80,7 +79,7 @@ const CardViewDirectory = ({
|
|||||||
<li className="list-inline-item me-1" style={{ fontSize: "10px" }}>
|
<li className="list-inline-item me-1" style={{ fontSize: "10px" }}>
|
||||||
<i className="bx bx-building bx-xs"></i>
|
<i className="bx bx-building bx-xs"></i>
|
||||||
</li>
|
</li>
|
||||||
<li className="list-inline-item" style={{ fontSize: "10px" }}>
|
<li className="list-inline-item text-small">
|
||||||
{contact.organization}
|
{contact.organization}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -96,52 +95,54 @@ const CardViewDirectory = ({
|
|||||||
>
|
>
|
||||||
<hr className="my-0" />
|
<hr className="my-0" />
|
||||||
{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 small-text">
|
<li className="list-inline-item text-small">
|
||||||
{contact.contactEmails[0]?.emailAddress}
|
{contact.contactEmails[0]?.emailAddress}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{contact.contactPhones[0] && (
|
{contact.contactPhones[0] && (
|
||||||
<ul className="list-inline m-0">
|
<ul className="list-inline m-0 ">
|
||||||
<li className="list-inline-item me-2">
|
<li className="list-inline-item me-1">
|
||||||
<i
|
<i
|
||||||
className={` ${getPhoneIcon(
|
className={` ${getPhoneIcon(
|
||||||
contact.contactPhones[0].label
|
contact.contactPhones[0].label
|
||||||
)} bx-xs`}
|
)} bx-xs`}
|
||||||
></i>
|
></i>
|
||||||
</li>
|
</li>
|
||||||
<li className="list-inline-item small-text">
|
<li className="list-inline-item text-small">
|
||||||
{contact.contactPhones[0]?.phoneNumber}
|
{contact.contactPhones[0]?.phoneNumber}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ul className="list-inline m-0">
|
<ul className="list-inline m-0">
|
||||||
<li className="list-inline-item me-2">
|
<li className="list-inline-item me-2 my-1">
|
||||||
<i className="bx bx-merge bx-xs"></i>
|
<i className="fa-solid fa-tag fs-6"></i>
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
<li className="list-inline-item small-text">
|
<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) => (
|
||||||
<React.Fragment key={bucketId}>
|
<li key={bucketId} className="list-inline-item me-1">
|
||||||
<li 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>
|
<i className="bx bx-pin bx-xs"></i>
|
||||||
</li>
|
<span className="small-text">
|
||||||
<li className="list-inline-item small-text">
|
{getBucketNameById(buckets, bucketId)}
|
||||||
{getBucketNameById(buckets, bucketId)}
|
</span>
|
||||||
</li>
|
</span>
|
||||||
</React.Fragment>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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()).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()).optional(),
|
bucketIds: z.array(z.string()).min(1,{message:"At least one Label required"}),
|
||||||
})
|
})
|
||||||
|
|
||||||
// .refine((data) => {
|
// .refine((data) => {
|
||||||
|
|||||||
147
src/components/Directory/EmployeeList.jsx
Normal file
147
src/components/Directory/EmployeeList.jsx
Normal 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;
|
||||||
@ -8,15 +8,36 @@ import Directory from "../../pages/Directory/Directory";
|
|||||||
import { DirectoryRepository } from "../../repositories/DirectoryRepository";
|
import { DirectoryRepository } from "../../repositories/DirectoryRepository";
|
||||||
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
import { cacheData, getCachedData } from "../../slices/apiDataManager";
|
||||||
import { useBuckets } from "../../hooks/useDirectory";
|
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 [bucketList, setBucketList] = useState([]);
|
||||||
|
const { employeesList } = useAllEmployees(false);
|
||||||
const { buckets } = useBuckets();
|
const { buckets, loading,refetch } = useBuckets();
|
||||||
const [action_bucket, setAction_bucket] = useState(false);
|
const [action_bucket, setAction_bucket] = useState(false);
|
||||||
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 [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 {
|
const {
|
||||||
register,
|
register,
|
||||||
@ -29,197 +50,290 @@ const ManageBucket = () =>
|
|||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
},
|
},
|
||||||
} );
|
});
|
||||||
|
|
||||||
const onSubmit = async (data) => {
|
|
||||||
setSubmitting(true);
|
|
||||||
try {
|
|
||||||
let response;
|
|
||||||
|
|
||||||
if ( selected_bucket )
|
const onSubmit = async (data) => {
|
||||||
{
|
setSubmitting(true);
|
||||||
let payload ={...data, id:selected_bucket.id}
|
try {
|
||||||
response = await DirectoryRepository.UpdateBuckets(selected_bucket.id, payload);
|
let response;
|
||||||
const cache_buckets = getCachedData("buckets") || [];
|
|
||||||
const updatedBuckets = cache_buckets.map((bucket) =>
|
if (selected_bucket) {
|
||||||
bucket.id === selected_bucket.id ? response?.data : bucket
|
let payload = { ...data, id: selected_bucket.id };
|
||||||
);
|
response = await DirectoryRepository.UpdateBuckets(
|
||||||
cacheData( "buckets", updatedBuckets );
|
selected_bucket.id,
|
||||||
setBucketList(updatedBuckets);
|
payload
|
||||||
showToast("Bucket Updated Successfully", "success");
|
);
|
||||||
} else {
|
const cache_buckets = getCachedData("buckets") || [];
|
||||||
response = await DirectoryRepository.CreateBuckets(data);
|
const updatedBuckets = cache_buckets.map((bucket) =>
|
||||||
const cache_buckets = getCachedData("buckets") || [];
|
bucket.id === selected_bucket.id ? response?.data : bucket
|
||||||
const updatedBuckets = [...cache_buckets, response?.data];
|
);
|
||||||
cacheData( "buckets", updatedBuckets );
|
cacheData("buckets", updatedBuckets);
|
||||||
setBucketList(updatedBuckets);
|
setBucketList(updatedBuckets);
|
||||||
showToast("Bucket Created Successfully", "success");
|
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();
|
const handleDeleteContact = async () => {
|
||||||
} catch (error) {
|
try {
|
||||||
const message =
|
const resp = await DirectoryRepository.DeleteBucket( deleteBucket );
|
||||||
error?.response?.data?.message ||
|
const cache_buckets = getCachedData("buckets") || [];
|
||||||
error?.message ||
|
const updatedBuckets = cache_buckets.filter((bucket) =>
|
||||||
"Error occurred during API call";
|
bucket.id != deleteBucket
|
||||||
showToast(message, "error");
|
);
|
||||||
}
|
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(() => {
|
useEffect(() => {
|
||||||
reset({
|
reset({
|
||||||
name: selected_bucket?.name || "",
|
name: selected_bucket?.name || "",
|
||||||
description: selected_bucket?.description || "",
|
description: selected_bucket?.description || "",
|
||||||
});
|
});
|
||||||
}, [ selected_bucket ] );
|
}, [selected_bucket]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setBucketList(buckets);
|
||||||
|
}, [buckets]);
|
||||||
|
|
||||||
useEffect( () =>
|
|
||||||
{
|
|
||||||
setBucketList( buckets )
|
|
||||||
}, [ buckets ] )
|
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
select_bucket(null);
|
select_bucket(null);
|
||||||
setAction_bucket(false);
|
setAction_bucket(false);
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sortedBucktesList = sortedBuckteList?.filter((bucket) => {
|
||||||
|
const term = searchTerm?.toLowerCase();
|
||||||
|
const name = bucket.name?.toLowerCase();
|
||||||
|
return name?.includes(term);
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container m-0 p-0" style={{ minHeight: "200px" }}>
|
<>
|
||||||
<div className="d-flex justify-content-center">
|
{deleteBucket && (
|
||||||
<p className="fs-h6 fw-semibold m-0">Manage Buckets</p>
|
<div
|
||||||
</div>
|
className={`modal fade ${deleteBucket ? "show" : ""}`}
|
||||||
<div className="d-flex justify-content-between px-2 px-sm-0 mt-5 mt-sm-1 align-items-center">
|
tabIndex="-1"
|
||||||
<i
|
role="dialog"
|
||||||
className={`fa-solid fa-arrow-left fs-5 cursor-pointer ${
|
style={{
|
||||||
action_bucket ? "" : "d-none"
|
display: deleteBucket ? "block" : "none",
|
||||||
}`}
|
backgroundColor: deleteBucket ? "rgba(0,0,0,0.5)" : "transparent",
|
||||||
onClick={handleBack}
|
}}
|
||||||
></i>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`btn btn-xs btn-primary ms-auto ${
|
|
||||||
action_bucket ? "d-none" : ""
|
|
||||||
}`}
|
|
||||||
onClick={() => setAction_bucket(true)}
|
|
||||||
>
|
>
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
<ConfirmModal
|
||||||
Add Bucket
|
type={"delete"}
|
||||||
</button>
|
header={"Delete Bucket"}
|
||||||
</div>
|
message={"Are you sure you want delete?"}
|
||||||
<div>
|
onSubmit={handleDeleteContact}
|
||||||
{!action_bucket ? (
|
onClose={() => setDeleteBucket(null)}
|
||||||
<div className="table-responsive text-nowrap pt-1 px-2 px-sm-0">
|
// loading={IsDeleting}
|
||||||
<table className="table px-2">
|
/>
|
||||||
<thead className="p-0">
|
</div>
|
||||||
<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>
|
|
||||||
|
|
||||||
<tbody className="table-border-bottom-0 overflow-auto">
|
<div className="container m-0 p-0" style={{ minHeight: "200px" }}>
|
||||||
{bucketList.map((bucket) => (
|
<div className="d-flex justify-content-center">
|
||||||
<tr key={bucket.id}>
|
<p className="fs-6 fw-semibold m-0">Manage Buckets</p>
|
||||||
<td colSpan={2} className="text-start">
|
</div>
|
||||||
{bucket.name}
|
<div className="d-flex justify-content-between px-2 px-sm-0 mt-5 mt-sm-1 align-items-center">
|
||||||
</td>
|
{action_bucket ? (
|
||||||
<td className="text-start d-none d-sm-table-cell">
|
<i
|
||||||
{bucket.description}
|
className={`fa-solid fa-arrow-left fs-5 cursor-pointer`}
|
||||||
</td>
|
onClick={handleBack}
|
||||||
<td className="justify-content-center">
|
></i>
|
||||||
<div className="d-flex justify-content-center align-items-center gap-2">
|
) : (
|
||||||
<i
|
<div className="d-flex align-items-center gap-2">
|
||||||
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>
|
|
||||||
<input
|
<input
|
||||||
|
type="search"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
{...register("name")}
|
placeholder="Search Bucket ..."
|
||||||
/>
|
value={searchTerm}
|
||||||
{errors.name && (
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
<small className="danger-text">{errors.name.message}</small>
|
/>
|
||||||
)}
|
<i
|
||||||
|
className={`bx bx-refresh cursor-pointer fs-4 ${loading ? "spin" : ""
|
||||||
|
}`}
|
||||||
|
title="Refresh"
|
||||||
|
onClick={() => rrefetch()}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="">
|
)}
|
||||||
<label className="form-label">Bucket Discription</label>
|
|
||||||
<textarea
|
<button
|
||||||
className="form-control form-control-sm"
|
type="button"
|
||||||
{...register("description")}
|
className={`btn btn-sm btn-primary ms-auto ${
|
||||||
/>
|
action_bucket ? "d-none" : ""
|
||||||
{errors.description && (
|
}`}
|
||||||
<small className="danger-text">
|
onClick={() => setAction_bucket(true)}
|
||||||
{errors.description.message}
|
>
|
||||||
</small>
|
<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>
|
||||||
<div className="mt-2 d-flex justify-content-center gap-3">
|
) : (
|
||||||
<button
|
<>
|
||||||
type="reset"
|
<form onSubmit={handleSubmit(onSubmit)} className="px-2 px-sm-0">
|
||||||
className="btn btn-sm btn-secondary"
|
<div className="">
|
||||||
disabled={isSubmitting}
|
<label className="form-label">Bucket Name</label>
|
||||||
>
|
<input
|
||||||
Cancel
|
className="form-control form-control-sm"
|
||||||
</button>
|
{...register("name")}
|
||||||
<button
|
/>
|
||||||
type="submit"
|
{errors.name && (
|
||||||
className="btn btn-sm btn-primary"
|
<small className="danger-text">{errors.name.message}</small>
|
||||||
disabled={isSubmitting}
|
)}
|
||||||
>
|
</div>
|
||||||
{isSubmitting ? "Please wait..." : "Submit"}
|
<div className="">
|
||||||
</button>
|
<label className="form-label">Bucket Discription</label>
|
||||||
</div>
|
<textarea
|
||||||
</form>
|
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>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -14,10 +14,11 @@ import useMaster, {
|
|||||||
} from "../../hooks/masterHook/useMaster";
|
} from "../../hooks/masterHook/useMaster";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { changeMaster } from "../../slices/localVariablesSlice";
|
import { changeMaster } from "../../slices/localVariablesSlice";
|
||||||
import { useBuckets } 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";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -25,13 +26,15 @@ 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 { 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();
|
||||||
@ -136,6 +139,7 @@ useEffect(() => {
|
|||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
submitContact(cleaned, reset, setSubmitting);
|
submitContact(cleaned, reset, setSubmitting);
|
||||||
};
|
};
|
||||||
|
const orgValue = watch("organization")
|
||||||
|
|
||||||
const handleClosed = () => {
|
const handleClosed = () => {
|
||||||
onCLosed();
|
onCLosed();
|
||||||
@ -160,10 +164,12 @@ useEffect(() => {
|
|||||||
|
|
||||||
<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>
|
||||||
<input
|
<InputSuggestions
|
||||||
className="form-control form-control-sm"
|
organizationList={organizationList}
|
||||||
{...register("organization")}
|
value={getValues("organization") || ""}
|
||||||
/>
|
onChange={(val) => setValue("organization", val)}
|
||||||
|
error={errors.organization?.message}
|
||||||
|
/>
|
||||||
{errors.organization && (
|
{errors.organization && (
|
||||||
<small className="danger-text">
|
<small className="danger-text">
|
||||||
{errors.organization.message}
|
{errors.organization.message}
|
||||||
@ -356,8 +362,7 @@ useEffect(() => {
|
|||||||
<label className="form-label ">Select Label</label>
|
<label className="form-label ">Select Label</label>
|
||||||
|
|
||||||
<ul
|
<ul
|
||||||
className="d-flex flex-wrap px-1 list-unstyled overflow-auto mb-0"
|
className="d-flex flex-wrap px-1 list-unstyled mb-0"
|
||||||
style={{ maxHeight: "80px" }}
|
|
||||||
>
|
>
|
||||||
{bucketsLoaging && <p>Loading...</p>}
|
{bucketsLoaging && <p>Loading...</p>}
|
||||||
{buckets?.map((item) => (
|
{buckets?.map((item) => (
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import NotesDirectory from "./NotesDirectory";
|
|||||||
|
|
||||||
const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
|
const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
|
||||||
const { conatProfile, loading } = useContactProfile(contact?.id);
|
const { conatProfile, loading } = useContactProfile(contact?.id);
|
||||||
const [activeTab, setActiveTab] = useState("profile");
|
|
||||||
const [profileContact, setProfileContact] = useState();
|
const [profileContact, setProfileContact] = useState();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -34,9 +33,11 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
|
|||||||
<i className="bx bx-building bx-xs"></i>{" "}
|
<i className="bx bx-building bx-xs"></i>{" "}
|
||||||
{conatProfile?.organization}
|
{conatProfile?.organization}
|
||||||
</span>
|
</span>
|
||||||
|
<span className="small-text">Manager</span>
|
||||||
</div>
|
</div>
|
||||||
</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 && (
|
{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" }}>
|
||||||
@ -61,7 +62,7 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ul className="list-inline mb-0">
|
<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}>
|
<li className="list-inline-item me-3" key={idx}>
|
||||||
<i className="bx bx-phone bx-xs me-1"></i>
|
<i className="bx bx-phone bx-xs me-1"></i>
|
||||||
{phone.phoneNumber}
|
{phone.phoneNumber}
|
||||||
@ -87,7 +88,28 @@ const ProfileContactDirectory = ({ contact, setOpen_contact, closeModal }) => {
|
|||||||
</div>
|
</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>
|
||||||
|
|
||||||
<hr className="my-1" />
|
<hr className="my-1" />
|
||||||
<NotesDirectory
|
<NotesDirectory
|
||||||
|
|||||||
@ -386,8 +386,8 @@ await submitContact({ ...cleaned, id: existingContact.id });
|
|||||||
<label className="form-label ">Select Label</label>
|
<label className="form-label ">Select Label</label>
|
||||||
|
|
||||||
<ul
|
<ul
|
||||||
className="d-flex flex-wrap px-1 list-unstyled overflow-auto mb-0"
|
className="d-flex flex-wrap px-1 list-unstyled mb-0"
|
||||||
style={{ maxHeight: "80px" }}
|
|
||||||
>
|
>
|
||||||
{bucketsLoaging && <p>Loading...</p>}
|
{bucketsLoaging && <p>Loading...</p>}
|
||||||
{buckets?.map((item) => (
|
{buckets?.map((item) => (
|
||||||
|
|||||||
35
src/components/common/FloatingMenu.css
Normal file
35
src/components/common/FloatingMenu.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/components/common/FloatingMenu.jsx
Normal file
33
src/components/common/FloatingMenu.jsx
Normal 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;
|
||||||
|
|
||||||
@ -54,7 +54,20 @@ useEffect(() => {
|
|||||||
// Dynamically generate data-bs attributes
|
// Dynamically generate data-bs attributes
|
||||||
const dataAttributesProps = Object.keys(dataAttributes).map(key => ({
|
const dataAttributesProps = Object.keys(dataAttributes).map(key => ({
|
||||||
[key]: dataAttributes[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 (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -65,8 +78,9 @@ useEffect(() => {
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
ref={modalRef} // Assign the ref to the modal element
|
ref={modalRef} // Assign the ref to the modal element
|
||||||
{...dataAttributesProps}
|
{...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-content">
|
||||||
<div className="modal-header p-0">
|
<div className="modal-header p-0">
|
||||||
{/* Close button inside the modal header */}
|
{/* Close button inside the modal header */}
|
||||||
|
|||||||
79
src/components/common/InputSuggestion.jsx
Normal file
79
src/components/common/InputSuggestion.jsx
Normal 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;
|
||||||
@ -1,21 +1,21 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from "react-hook-form";
|
||||||
import './MultiSelectDropdown.css';
|
import "./MultiSelectDropdown.css";
|
||||||
|
|
||||||
const SelectMultiple = ({
|
const SelectMultiple = ({
|
||||||
name,
|
name,
|
||||||
options = [],
|
options = [],
|
||||||
label = 'Select options',
|
label = "Select options",
|
||||||
labelKey = 'name',
|
labelKey = "name",
|
||||||
valueKey = 'id',
|
valueKey = "id",
|
||||||
placeholder = 'Please select...',
|
placeholder = "Please select...",
|
||||||
IsLoading = false
|
IsLoading = false,
|
||||||
}) => {
|
}) => {
|
||||||
const { setValue, watch } = useFormContext();
|
const { setValue, watch } = useFormContext();
|
||||||
const selectedValues = watch(name) || [];
|
const selectedValues = watch(name) || [];
|
||||||
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [searchText, setSearchText] = useState('');
|
const [searchText, setSearchText] = useState("");
|
||||||
const dropdownRef = useRef(null);
|
const dropdownRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -24,8 +24,8 @@ const SelectMultiple = ({
|
|||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleCheckboxChange = (value) => {
|
const handleCheckboxChange = (value) => {
|
||||||
@ -44,35 +44,37 @@ const SelectMultiple = ({
|
|||||||
<div ref={dropdownRef} className="multi-select-dropdown-container">
|
<div ref={dropdownRef} className="multi-select-dropdown-container">
|
||||||
<label className="form-label mb-1">{label}</label>
|
<label className="form-label mb-1">{label}</label>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="multi-select-dropdown-header"
|
className="multi-select-dropdown-header"
|
||||||
onClick={() => setIsOpen((prev) => !prev)}
|
onClick={() => setIsOpen((prev) => !prev)}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={
|
className={
|
||||||
selectedValues.length > 0
|
selectedValues.length > 0
|
||||||
? 'placeholder-style-selected'
|
? "placeholder-style-selected"
|
||||||
: 'placeholder-style'
|
: "placeholder-style"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="selected-badges-container">
|
<div className="selected-badges-container">
|
||||||
{selectedValues.length > 0 ? (
|
{selectedValues.length > 0 ? (
|
||||||
selectedValues.map((val) => {
|
selectedValues.map((val) => {
|
||||||
const found = options.find((opt) => opt[valueKey] === val);
|
const found = options.find((opt) => opt[valueKey] === val);
|
||||||
return (
|
return (
|
||||||
<span key={val} className="badge badge-selected-item mx-1">
|
<span
|
||||||
{found ? found[labelKey] : ''}
|
key={val}
|
||||||
</span>
|
className="badge badge-selected-item mx-1 mb-1"
|
||||||
);
|
>
|
||||||
})
|
{found ? found[labelKey] : ""}
|
||||||
) : (
|
</span>
|
||||||
<span className="placeholder-text">{placeholder}</span>
|
);
|
||||||
)}
|
})
|
||||||
</div>
|
) : (
|
||||||
</span>
|
<span className="placeholder-text">{placeholder}</span>
|
||||||
<i className="bx bx-chevron-down"></i>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</span>
|
||||||
|
<i className="bx bx-chevron-down"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div className="multi-select-dropdown-options">
|
<div className="multi-select-dropdown-options">
|
||||||
@ -94,7 +96,9 @@ const SelectMultiple = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={valueVal}
|
key={valueVal}
|
||||||
className={`multi-select-dropdown-option ${isChecked ? 'selected' : ''}`}
|
className={`multi-select-dropdown-option ${
|
||||||
|
isChecked ? "selected" : ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -105,14 +109,16 @@ const SelectMultiple = ({
|
|||||||
<label className="text-secondary">{labelVal}</label>
|
<label className="text-secondary">{labelVal}</label>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} )}
|
})}
|
||||||
{!IsLoading && filteredOptions.length === 0 && (
|
{!IsLoading && filteredOptions.length === 0 && (
|
||||||
<div className='multi-select-dropdown-Not-found'>
|
<div className="multi-select-dropdown-Not-found">
|
||||||
<label className="text-muted">Not Found {`'${searchText}'`}</label>
|
<label className="text-muted">
|
||||||
|
Not Found {`'${searchText}'`}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{IsLoading && filteredOptions.length === 0 && (
|
{IsLoading && filteredOptions.length === 0 && (
|
||||||
<div className='multi-select-dropdown-Not-found'>
|
<div className="multi-select-dropdown-Not-found">
|
||||||
<label className="text-muted">Loading...</label>
|
<label className="text-muted">Loading...</label>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -37,47 +37,46 @@ export const useDirectory = (isActive) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const useBuckets = () => {
|
export const useBuckets = () => {
|
||||||
const [buckets, setBuckets] = useState([]);
|
const [buckets, setBuckets] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
const fetchBuckets = async () => {
|
const fetchBuckets = async () => {
|
||||||
const cacheBuckets = getCachedData("buckets");
|
setLoading(true);
|
||||||
if (!cacheBuckets) {
|
try {
|
||||||
setLoading( true );
|
const resp = await DirectoryRepository.GetBucktes();
|
||||||
try {
|
setBuckets(resp.data);
|
||||||
const resp = await DirectoryRepository.GetBucktes();
|
cacheData("buckets", resp.data);
|
||||||
setBuckets(resp.data);
|
setLoading(false);
|
||||||
cacheData( "buckets", resp.data );
|
} catch (error) {
|
||||||
setLoading(false);
|
const msg =
|
||||||
} catch (error) {
|
error?.response?.data?.message ||
|
||||||
const msg =
|
error?.message ||
|
||||||
error?.response?.data?.message || error?.message || "Something went wrong";
|
"Something went wrong";
|
||||||
setError(msg);
|
setError( msg );
|
||||||
}
|
setLoading(false);
|
||||||
} else {
|
|
||||||
setBuckets(cacheBuckets);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
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) =>
|
export const useContactProfile = (id) => {
|
||||||
{
|
const [conatProfile, 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");
|
const cached = getCachedData("Contact Profile");
|
||||||
|
|
||||||
if (!cached || cached.contactId !== id) {
|
if (!cached || cached.contactId !== id) {
|
||||||
@ -88,7 +87,9 @@ const fetchContactProfile = async () => {
|
|||||||
cacheData("Contact Profile", { data: resp.data, contactId: id });
|
cacheData("Contact Profile", { data: resp.data, contactId: id });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const msg =
|
const msg =
|
||||||
err?.response?.data?.message || err?.message || "Something went wrong";
|
err?.response?.data?.message ||
|
||||||
|
err?.message ||
|
||||||
|
"Something went wrong";
|
||||||
setError(msg);
|
setError(msg);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -99,35 +100,33 @@ const fetchContactProfile = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if ( id )
|
if (id) {
|
||||||
{
|
|
||||||
fetchContactProfile(id);
|
fetchContactProfile(id);
|
||||||
}
|
}
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
return { conatProfile, loading, Error };
|
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 fetchContactNotes = async () => {
|
||||||
{
|
|
||||||
const [ contactNotes, setContactNotes ] = useState( [] );
|
|
||||||
const [ loading, setLoading ] = useState( false );
|
|
||||||
const [ Error, setError ] = useState( "" );
|
|
||||||
|
|
||||||
|
|
||||||
const fetchContactNotes = async () => {
|
|
||||||
const cached = getCachedData("Contact Notes");
|
const cached = getCachedData("Contact Notes");
|
||||||
|
|
||||||
if (!cached || cached.contactId !== id) {
|
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);
|
||||||
setContactNotes(resp.data);
|
setContactNotes(resp.data);
|
||||||
cacheData("Contact Notes", { data: resp.data, contactId: id });
|
cacheData("Contact Notes", { data: resp.data, contactId: id });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const msg =
|
const msg =
|
||||||
err?.response?.data?.message || err?.message || "Something went wrong";
|
err?.response?.data?.message ||
|
||||||
|
err?.message ||
|
||||||
|
"Something went wrong";
|
||||||
setError(msg);
|
setError(msg);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -138,11 +137,43 @@ const fetchContactNotes = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if ( id )
|
if (id) {
|
||||||
{
|
|
||||||
fetchContactNotes(id);
|
fetchContactNotes(id);
|
||||||
}
|
}
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
return { contactNotes, loading, Error };
|
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 };
|
||||||
|
};
|
||||||
|
|||||||
35
src/hooks/useSortableData.js
Normal file
35
src/hooks/useSortableData.js
Normal 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 };
|
||||||
|
};
|
||||||
@ -4,25 +4,30 @@ import Header from "../components/Layout/Header";
|
|||||||
import Sidebar from "../components/Layout/Sidebar";
|
import Sidebar from "../components/Layout/Sidebar";
|
||||||
|
|
||||||
import Footer from "../components/Layout/Footer";
|
import Footer from "../components/Layout/Footer";
|
||||||
|
import FloatingMenu from "../components/common/FloatingMenu";
|
||||||
|
import { FabProvider } from "../Context/FabContext";
|
||||||
|
|
||||||
const HomeLayout = () => {
|
const HomeLayout = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Main();
|
Main();
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div className="layout-wrapper layout-content-navbar" >
|
<FabProvider>
|
||||||
<div className="layout-container" >
|
<div className="layout-wrapper layout-content-navbar">
|
||||||
<Sidebar />
|
<div className="layout-container">
|
||||||
<div className="layout-page ">
|
<Sidebar />
|
||||||
<Header />
|
<div className="layout-page ">
|
||||||
<div className="content-wrapper" >
|
<Header />
|
||||||
<Outlet />
|
<div className="content-wrapper">
|
||||||
<Footer />
|
<Outlet />
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<FloatingMenu />
|
||||||
|
<div className="layout-overlay layout-menu-toggle"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="layout-overlay layout-menu-toggle"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</FabProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import ConfirmModal from "../../components/common/ConfirmModal";
|
|||||||
import DirectoryListTableHeader from "./DirectoryListTableHeader";
|
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";
|
||||||
|
|
||||||
const Directory = () =>
|
const Directory = () =>
|
||||||
{
|
{
|
||||||
@ -36,11 +37,12 @@ const Directory = () =>
|
|||||||
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 { contacts, loading } = useDirectory(IsActive);
|
const { contacts, loading , refetch} = useDirectory(IsActive);
|
||||||
const { contactCategory, loading: contactCategoryLoading } =
|
const { contactCategory, loading: contactCategoryLoading } =
|
||||||
useContactCategory();
|
useContactCategory();
|
||||||
const {buckets} = useBuckets();
|
const {buckets} = useBuckets();
|
||||||
|
|
||||||
const submitContact = async (data) => {
|
const submitContact = async (data) => {
|
||||||
@ -56,15 +58,17 @@ const Directory = () =>
|
|||||||
);
|
);
|
||||||
showToast("Contact updated successfully", "success");
|
showToast("Contact updated successfully", "success");
|
||||||
setIsOpenModal(false);
|
setIsOpenModal(false);
|
||||||
setSelectedContact(null);
|
setSelectedContact( null );
|
||||||
} else {
|
} else {
|
||||||
response = await DirectoryRepository.CreateContact(data);
|
response = await DirectoryRepository.CreateContact(data);
|
||||||
updatedContacts = [...contacts_cache, response.data];
|
updatedContacts = [...contacts_cache, response.data];
|
||||||
showToast("Contact created successfully", "success");
|
showToast("Contact created successfully", "success");
|
||||||
setIsOpenModal(false);
|
setIsOpenModal(false);
|
||||||
}
|
}
|
||||||
cacheData("Contacts", {data:updatedContacts,isActive:IsActive});
|
|
||||||
setContactList(updatedContacts);
|
// cacheData("Contacts", {data:updatedContacts,isActive:IsActive});
|
||||||
|
// setContactList(updatedContacts);
|
||||||
|
refetch()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const msg =
|
const msg =
|
||||||
error.response?.data?.message ||
|
error.response?.data?.message ||
|
||||||
@ -80,7 +84,7 @@ const Directory = () =>
|
|||||||
const contacts_cache = getCachedData("contacts")?.data || [];
|
const contacts_cache = getCachedData("contacts")?.data || [];
|
||||||
|
|
||||||
const response = await DirectoryRepository.DeleteContact(deleteContact);
|
const response = await DirectoryRepository.DeleteContact(deleteContact);
|
||||||
const updatedContacts = ContactList.filter((c) => c.id !== deleteContact);
|
const updatedContacts = ContactList.filter( ( c ) => c.id !== deleteContact );
|
||||||
setContactList(updatedContacts);
|
setContactList(updatedContacts);
|
||||||
cacheData("Contacts", {data:updatedContacts,isActive:IsActive});
|
cacheData("Contacts", {data:updatedContacts,isActive:IsActive});
|
||||||
showToast("Contact deleted successfully", "success");
|
showToast("Contact deleted successfully", "success");
|
||||||
@ -155,7 +159,7 @@ const Directory = () =>
|
|||||||
|
|
||||||
return matchesSearch && matchesCategory && matchesBucket;
|
return matchesSearch && matchesCategory && matchesBucket;
|
||||||
}).sort((a, b) => a.name.localeCompare(b.name));
|
}).sort((a, b) => a.name.localeCompare(b.name));
|
||||||
}, [ContactList, searchText, selectedCategoryIds, selectedBucketIds]);
|
}, [ContactList, searchText, selectedCategoryIds, selectedBucketIds,selectedContact]);
|
||||||
|
|
||||||
const applyFilter = () => {
|
const applyFilter = () => {
|
||||||
setSelectedBucketIds(tempSelectedBucketIds);
|
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 (
|
return (
|
||||||
<div className="container-xxl flex-grow-1 container-p-y">
|
<div className="container-xxl flex-grow-1 container-p-y">
|
||||||
|
|
||||||
<Breadcrumb
|
<Breadcrumb
|
||||||
data={[
|
data={[
|
||||||
{ label: "Home", link: "/dashboard" },
|
{ label: "Home", link: "/dashboard" },
|
||||||
@ -257,13 +283,13 @@ const Directory = () =>
|
|||||||
<GlobalModel
|
<GlobalModel
|
||||||
isOpen={openBucketModal}
|
isOpen={openBucketModal}
|
||||||
closeModal={() =>setOpenBucketModal(false)}
|
closeModal={() =>setOpenBucketModal(false)}
|
||||||
size="md"
|
size="lg"
|
||||||
>
|
>
|
||||||
<ManageBucket buckets={buckets} />
|
<ManageBucket buckets={buckets} />
|
||||||
</GlobalModel>
|
</GlobalModel>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="card p-2">
|
<div className="card p-2 card-minHeight">
|
||||||
<DirectoryPageHeader
|
<DirectoryPageHeader
|
||||||
searchText={searchText}
|
searchText={searchText}
|
||||||
setSearchText={setSearchText}
|
setSearchText={setSearchText}
|
||||||
@ -333,6 +359,8 @@ const Directory = () =>
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{!loading && currentItems < ITEMS_PER_PAGE && (
|
{!loading && currentItems < ITEMS_PER_PAGE && (
|
||||||
<nav aria-label="Page ">
|
<nav aria-label="Page ">
|
||||||
|
|||||||
@ -11,43 +11,22 @@ const DirectoryListTableHeader = ( {children, IsActive} ) =>
|
|||||||
<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">
|
||||||
<IconButton
|
|
||||||
size={12}
|
|
||||||
iconClass="bx bx-user"
|
|
||||||
color="secondary"
|
|
||||||
onClick={() => alert("User icon clicked")}
|
|
||||||
/>
|
|
||||||
<span>Name</span>
|
<span>Name</span>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="px-2 text-start">
|
<th className="px-2 text-start">
|
||||||
<div className="d-flex text-center align-items-center gap-1 justify-content-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>
|
<span>Email</span>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="mx-2">
|
<th className="mx-2">
|
||||||
<div className="d-flex align-items-center m-0 p-0 gap-1">
|
<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>
|
<span>Phone</span>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th className="mx-2">
|
<th className="mx-2">
|
||||||
<div className="d-flex align-items-center gap-1">
|
<div className="d-flex align-items-center gap-1">
|
||||||
<IconButton
|
|
||||||
size={12}
|
|
||||||
iconClass="bx bxs-grid-alt"
|
|
||||||
color="info"
|
|
||||||
/>
|
|
||||||
<span>Organization</span>
|
<span>Organization</span>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
const DirectoryPageHeader = ({
|
const DirectoryPageHeader = ({
|
||||||
searchText,
|
searchText,
|
||||||
@ -16,14 +16,19 @@ const DirectoryPageHeader = ({
|
|||||||
applyFilter,
|
applyFilter,
|
||||||
loading,
|
loading,
|
||||||
IsActive,
|
IsActive,
|
||||||
setIsOpenModal,
|
setIsOpenModal,
|
||||||
setOpenBucketModal
|
setOpenBucketModal,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [filtered, setFiltered] = useState();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setFiltered(
|
||||||
|
tempSelectedBucketIds?.length + tempSelectedCategoryIds?.length
|
||||||
|
);
|
||||||
|
}, [tempSelectedBucketIds, tempSelectedCategoryIds]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="row">
|
<div className="row"></div>
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className="row mx-0 px-0 align-items-center">
|
<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 ">
|
<div className="col-12 col-md-4 mb-2 px-1 d-flex align-items-center ">
|
||||||
<input
|
<input
|
||||||
@ -66,22 +71,31 @@ const DirectoryPageHeader = ({
|
|||||||
<div className="dropdown" style={{ width: "fit-content" }}>
|
<div className="dropdown" style={{ width: "fit-content" }}>
|
||||||
<div className="dropdown" style={{ width: "fit-content" }}>
|
<div className="dropdown" style={{ width: "fit-content" }}>
|
||||||
<a
|
<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"
|
data-bs-toggle="dropdown"
|
||||||
aria-expanded="false"
|
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>
|
</a>
|
||||||
|
|
||||||
<ul className="dropdown-menu p-3" style={{ width: "320px" }}>
|
<ul className="dropdown-menu p-3" style={{ width: "320px" }}>
|
||||||
<div>
|
<div>
|
||||||
<p className="small-text fw-semibold text-muted m-0">
|
<p className="text-small text-muted m-0">
|
||||||
Filter by
|
Filter by
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Bucket Filter */}
|
{/* Bucket Filter */}
|
||||||
<div className="mt-1">
|
<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">
|
<div className="d-flex flex-wrap">
|
||||||
{filteredBuckets.map(({ id, name }) => (
|
{filteredBuckets.map(({ id, name }) => (
|
||||||
<div
|
<div
|
||||||
@ -97,7 +111,7 @@ const DirectoryPageHeader = ({
|
|||||||
onChange={() => handleTempBucketChange(id)}
|
onChange={() => handleTempBucketChange(id)}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
className="form-check-label text-nowrap small-text "
|
className="form-check-label text-nowrap text-small "
|
||||||
htmlFor={`bucket-${id}`}
|
htmlFor={`bucket-${id}`}
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
@ -109,7 +123,7 @@ const DirectoryPageHeader = ({
|
|||||||
<hr className="m-0" />
|
<hr className="m-0" />
|
||||||
{/* Category Filter */}
|
{/* Category Filter */}
|
||||||
<div className="mt-1">
|
<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">
|
<div className="d-flex flex-wrap">
|
||||||
{filteredCategories.map(({ id, name }) => (
|
{filteredCategories.map(({ id, name }) => (
|
||||||
<div
|
<div
|
||||||
@ -125,7 +139,7 @@ const DirectoryPageHeader = ({
|
|||||||
onChange={() => handleTempCategoryChange(id)}
|
onChange={() => handleTempCategoryChange(id)}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
className="form-check-label text-nowrap small-text"
|
className="form-check-label text-nowrap text-small"
|
||||||
htmlFor={`cat-${id}`}
|
htmlFor={`cat-${id}`}
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
@ -154,49 +168,69 @@ const DirectoryPageHeader = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 col-md-8 mb-2 px-1 text-md-end 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">
|
|
||||||
<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>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-xs btn-primary"
|
className="btn btn-sm btn-primary"
|
||||||
onClick={() => setIsOpenModal(true)}
|
onClick={() => setIsOpenModal(true)}
|
||||||
>
|
>
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
<i className="bx bx-plus-circle me-2"></i>
|
||||||
New Contact
|
New Contact
|
||||||
</button>
|
</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>
|
||||||
<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>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DirectoryPageHeader;
|
export default DirectoryPageHeader;
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import {api} from "../utils/axiosClient";
|
import {api} from "../utils/axiosClient";
|
||||||
|
|
||||||
export const DirectoryRepository = {
|
export const DirectoryRepository = {
|
||||||
|
GetOrganizations:()=>api.get('/api/directory/organization'),
|
||||||
|
|
||||||
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 ),
|
||||||
@ -8,7 +10,8 @@ export const DirectoryRepository = {
|
|||||||
|
|
||||||
GetBucktes: () => api.get( `/api/directory/buckets` ),
|
GetBucktes: () => api.get( `/api/directory/buckets` ),
|
||||||
CreateBuckets: ( data ) => api.post( `/api/Directory/bucket`, data ),
|
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 }` ),
|
GetContactProfile: ( id ) => api.get( `/api/directory/profile/${ id }` ),
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user