diff --git a/src/components/Directory/EmployeeList.jsx b/src/components/Directory/EmployeeList.jsx index 1cb1f9a9..fb644374 100644 --- a/src/components/Directory/EmployeeList.jsx +++ b/src/components/Directory/EmployeeList.jsx @@ -1,25 +1,57 @@ -import React, { useState } from "react"; -import { useSortableData } from "../../hooks/useSortableData"; - -const EmployeeList = ({ employees }) => { - const [selectedIds, setSelectedIds] = useState([]); +import React,{useState,useEffect} from 'react' +import {useSortableData} from '../../hooks/useSortableData'; +const EmployeeList = ( {employees, onChange, assignedEmployee = []} ) => +{ + const [employeefiltered, setEmployeeFilter] = useState([]); + const [employeeStatusList, setEmployeeStatusList] = useState([]); const [searchTerm, setSearchTerm] = useState(""); + // Populate filtered list on load + useEffect(() => { + setEmployeeFilter(employees?.filter((emp) => emp.email != null) || []); + }, [employees]); + + // Initialize checked employees based on assignedEmployee prop + useEffect(() => { + if (Array.isArray(assignedEmployee)) { + const initialStatus = assignedEmployee.map((id) => ({ + employeeId: id, + isActive: true, + })); + setEmployeeStatusList(initialStatus); + } + }, [assignedEmployee]); + + // Send updated list to parent + useEffect(() => { + if (onChange) { + onChange(employeeStatusList); + } + }, [employeeStatusList]); const handleCheckboxChange = (id) => { - setSelectedIds((prev) => - prev.includes(id) ? prev?.filter((empId) => empId !== id) : [...prev, id] - ); + setEmployeeStatusList((prev) => { + const exists = prev.find((emp) => emp.employeeId === id); + if (exists) { + return prev.map((emp) => + emp.employeeId === id ? { ...emp, isActive: !emp.isActive } : emp + ); + } else { + return [...prev, { employeeId: id, isActive: true }]; + } + }); }; - const getSelectedEmployees = () => { - console.log("Selected Employee IDs:", selectedIds); + const isChecked = (id) => { + const found = employeeStatusList.find((emp) => emp.employeeId === id); + return found?.isActive || false; }; + // Sorting const { items: sortedEmployees, requestSort, sortConfig, - } = useSortableData(employees, { + } = useSortableData(employeefiltered, { key: (e) => `${e?.firstName} ${e?.lastName}`, direction: "asc", }); @@ -34,21 +66,13 @@ const EmployeeList = ({ employees }) => { }; 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) + const fullName = `${employee?.firstName} ${employee?.lastName}`?.toLowerCase(); + return fullName.includes(searchTerm.toLowerCase()); }); - return ( <> -
-

Add Employee

+
+

Add Employee

{ > Name {getSortIcon()} - Role - Status - Bucket + Email {employees.length === 0 ? ( - -
- No Employee Available -
+ +
+ No Employee Available +
) : filteredEmployees.length === 0 ? ( - -
- No Matchinng Employee Found. -
+ +
+ No Matching Employee Found. +
) : ( @@ -103,36 +125,18 @@ const EmployeeList = ({ employees }) => { handleCheckboxChange(employee.id)} />
-

+

{`${employee.firstName} ${employee.lastName}`}

- {employee.email}
- {employee.jobRole} - - - - {employee.isActive ? "Active" : "Inactive"} - - - - - {" "} - {employee.jobRole} - + {employee.email} )) diff --git a/src/components/Directory/ListViewDirectory.jsx b/src/components/Directory/ListViewDirectory.jsx index ede641fe..2258fb5b 100644 --- a/src/components/Directory/ListViewDirectory.jsx +++ b/src/components/Directory/ListViewDirectory.jsx @@ -5,73 +5,69 @@ import { getEmailIcon,getPhoneIcon } from './DirectoryUtils'; const ListViewDirectory = ({IsActive, contact,setSelectedContact,setIsOpenModal,setOpen_contact,setIsOpenModalNote,IsDeleted}) => { return ( - - - { - if ( IsActive ) - { - setIsOpenModalNote(true) - setOpen_contact(contact) - } - }}> -
- + + { + if (IsActive) { + setIsOpenModalNote(true); + setOpen_contact(contact); + } + }}> +
+ + {contact?.name || ""} +
+ - {contact?.name || ""} -
+ +
+ {contact.contactEmails?.map((email, index) => ( + + + {email.emailAddress} + + ))} +
+ + +
+ {contact.contactPhones?.map((phone, index) => ( + + + {phone.phoneNumber} + + ))} +
+ - + + {contact.organization} + - {/* Emails */} - -
- {contact.contactEmails?.map((email, index) => ( - - - {email.emailAddress} - - ))} -
- + + + {contact?.contactCategory?.name} + + - -
- {contact.contactPhones?.map((phone, index) => ( - - - {phone.phoneNumber} - - ))} -
- + {IsActive && ( + + { + setSelectedContact(contact); + setIsOpenModal(true); + }}> + IsDeleted(contact.id)}> + + )} + - - {contact.organization} - - - -
- {contact?.contactCategory?.name } -
- - - {/* Actions */} - {IsActive && - - - { - setSelectedContact( contact ) - setIsOpenModal( true ) - }}> - IsDeleted( contact.id )}> - - } - ); }; diff --git a/src/components/Directory/ManageBucket.jsx b/src/components/Directory/ManageBucket.jsx index 60f59e5b..991d0340 100644 --- a/src/components/Directory/ManageBucket.jsx +++ b/src/components/Directory/ManageBucket.jsx @@ -16,7 +16,8 @@ import ConfirmModal from "../common/ConfirmModal"; const ManageBucket = () => { const [bucketList, setBucketList] = useState([]); const { employeesList } = useAllEmployees(false); - const { buckets, loading,refetch } = useBuckets(); + const [selectedEmployee, setSelectEmployee] = useState([]); + const { buckets, loading, refetch } = useBuckets(); const [action_bucket, setAction_bucket] = useState(false); const [isSubmitting, setSubmitting] = useState(false); const [selected_bucket, select_bucket] = useState(null); @@ -54,28 +55,88 @@ const ManageBucket = () => { const onSubmit = async (data) => { setSubmitting(true); + try { + const cache_buckets = getCachedData("buckets") || []; let response; + // Utility: Compare arrays regardless of order + const arraysAreEqual = (a, b) => { + if (a.length !== b.length) return false; + const setA = new Set(a); + const setB = new Set(b); + return [...setA].every((id) => setB.has(id)); + }; + + // UPDATE existing bucket if (selected_bucket) { - let payload = { ...data, id: selected_bucket.id }; + const payload = { ...data, id: selected_bucket.id }; + + // 1. Update bucket details 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); + + // 2. Update employee assignments if they changed + const existingEmployeeIds = selected_bucket?.employeeIds || []; + const employeesToUpdate = selectedEmployee.filter((emp) => { + const isExisting = existingEmployeeIds.includes(emp.employeeId); + return (!isExisting && emp.isActive) || (isExisting && !emp.isActive); + }); + + // Create a filtered list of active employee IDs to compare + const newActiveEmployeeIds = selectedEmployee + .filter((emp) => { + const isExisting = existingEmployeeIds.includes(emp.employeeId); + return ( + (!isExisting && emp.isActive) || (isExisting && !emp.isActive) + ); + }) + .map((emp) => emp.employeeId); + + if ( + !arraysAreEqual(newActiveEmployeeIds, existingEmployeeIds) && + employeesToUpdate.length != 0 + ) { + try { + response = await DirectoryRepository.AssignedBuckets( + selected_bucket.id, + employeesToUpdate + ); + } catch (assignError) { + const assignMessage = + assignError?.response?.data?.message || + assignError?.message || + "Error assigning employees."; + showToast(assignMessage, "error"); + } + } + const updatedData = cache_buckets?.map((bucket) => + bucket.id === response?.data?.id ? response.data : bucket + ); + + cacheData("buckets", updatedData); + + setBucketList(updatedData); showToast("Bucket Updated Successfully", "success"); - } else { + } + + // CREATE new bucket + 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"); } @@ -86,18 +147,20 @@ const ManageBucket = () => { error?.message || "Error occurred during API call"; showToast(message, "error"); + } finally { + setSubmitting(false); } }; 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); + 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) { @@ -131,7 +194,6 @@ const ManageBucket = () => { const name = bucket.name?.toLowerCase(); return name?.includes(term); }); - return ( <> {deleteBucket && ( @@ -173,19 +235,20 @@ const ManageBucket = () => { placeholder="Search Bucket ..." value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} - /> - rrefetch()} - /> + /> + refetch()} + />
)}
+ Contacts
Action @@ -251,36 +315,43 @@ const ManageBucket = () => { )} - {sortedBucktesList.map((bucket) => ( - - - {" "} - {bucket.name} - - - {bucket.description} - - -
- { - select_bucket(bucket); - setAction_bucket(true); - }} - > - setDeleteBucket(bucket?.id)} - > - -
- - - ))} + {!loading && + sortedBucktesList.map((bucket) => ( + + + {" "} + {bucket.name} + + + {bucket.description} + + {bucket.numberOfContacts} + +
+ { + select_bucket(bucket); + setAction_bucket(true); + }} + > + setDeleteBucket(bucket?.id)} + > +
+ + + ))}
@@ -310,9 +381,18 @@ const ManageBucket = () => { )} + + {selected_bucket && ( + setSelectEmployee(data)} + assignedEmployee={selected_bucket?.employeeIds} + /> + )} +
- - )} diff --git a/src/components/Directory/NotesDirectory.jsx b/src/components/Directory/NotesDirectory.jsx index 5a3c38a4..1c94d9d6 100644 --- a/src/components/Directory/NotesDirectory.jsx +++ b/src/components/Directory/NotesDirectory.jsx @@ -10,18 +10,16 @@ import moment from "moment"; import { cacheData, getCachedData } from "../../slices/apiDataManager"; import NoteCardDirectory from "./NoteCardDirectory"; import showToast from "../../services/toastService"; -import {useContactNotes} from "../../hooks/useDirectory"; +import { useContactNotes } from "../../hooks/useDirectory"; const schema = z.object({ note: z.string().min(1, { message: "Note is required" }), }); -const NotesDirectory = ( {isLoading, contactProfile, setProfileContact} ) => -{ - const [ IsActive, setIsActive ] = useState( true ) - const {contactNotes} = useContactNotes(contactProfile?.id,!IsActive) +const NotesDirectory = ({ isLoading, contactProfile, setProfileContact }) => { + const [IsActive, setIsActive] = useState(true); + const { contactNotes } = useContactNotes(contactProfile?.id, !IsActive); - const [NotesData, setNotesData] = useState(); const [IsSubmitting, setIsSubmitting] = useState(false); const [addNote, setAddNote] = useState(false); @@ -72,8 +70,8 @@ const NotesDirectory = ( {isLoading, contactProfile, setProfileContact} ) => setValue("note", ""); setIsSubmitting(false); showToast("Note added successfully!", "success"); - setAddNote( false ); - setIsActive(true) + setAddNote(false); + setIsActive(true); } catch (error) { setIsSubmitting(false); const msg = @@ -93,31 +91,35 @@ const NotesDirectory = ( {isLoading, contactProfile, setProfileContact} ) =>

Notes :

- - setAddNote( !addNote )} - > - {/* + {/* */} + + + Show Inactive Notes + + setAddNote(!addNote)} + > + {/* */} - {addNote ? "close" : "Add Note"} - -
+ {addNote ? "close" : "Add Note"} + +
{addNote && (
@@ -141,7 +143,7 @@ const NotesDirectory = ( {isLoading, contactProfile, setProfileContact} ) => )} {!isLoading && - [...(IsActive ? contactProfile?.notes || [] : contactNotes || [])] + [...(IsActive ? contactProfile?.notes || [] : contactNotes || [])] .reverse() .map((noteItem) => ( setProfileContact={setProfileContact} key={noteItem.id} /> - ) )} - {IsActive && (

{!isLoading && contactProfile?.notes.length == 0 && !addNote && (

No Notes Found

)}

)} + ))} + {IsActive && ( +

+ {!isLoading && contactProfile?.notes.length == 0 && !addNote && ( +

No Notes Found

+ )} +

+ )} {!IsActive && ( -

{!isLoading && contactNotes.length == 0 && !addNote && (

No Notes Found

) }

- +

+ {!isLoading && contactNotes.length == 0 && !addNote && ( +

No Notes Found

+ )} +

)} diff --git a/src/components/Directory/UpdateContact.jsx b/src/components/Directory/UpdateContact.jsx index 6c9e75a9..3878dc47 100644 --- a/src/components/Directory/UpdateContact.jsx +++ b/src/components/Directory/UpdateContact.jsx @@ -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"; const UpdateContact = ({ submitContact, existingContact, onCLosed }) => { const selectedMaster = useSelector( @@ -34,6 +35,7 @@ const UpdateContact = ({ submitContact, existingContact, onCLosed }) => { const [ IsSubmitting, setSubmitting ] = useState( false ); const [isInitialized, setIsInitialized] = useState(false); const dispatch = useDispatch(); + const {organizationList} = useOrganization() const methods = useForm({ resolver: zodResolver(ContactSchema), @@ -137,7 +139,7 @@ await submitContact({ ...cleaned, id: existingContact.id }); setSubmitting(false); }; - + const orgValue = watch("organization") const handleClosed = () => { onCLosed(); }; @@ -193,12 +195,15 @@ await submitContact({ ...cleaned, id: existingContact.id }); )} +
- + setValue("organization", val)} + error={errors.organization?.message} + /> {errors.organization && ( {errors.organization.message} diff --git a/src/components/Project/Teams.jsx b/src/components/Project/Teams.jsx index 0ff82929..cbd0505e 100644 --- a/src/components/Project/Teams.jsx +++ b/src/components/Project/Teams.jsx @@ -43,7 +43,6 @@ const Teams = ({ project }) => { .then((response) => { setEmployees(response.data); setFilteredEmployees( response.data.filter( ( emp ) => emp.isActive ) ); - console.log(response) setEmployeeLoading(false); }) .catch((error) => { diff --git a/src/components/common/Avatar.jsx b/src/components/common/Avatar.jsx index 02e6e275..5c3f25e6 100644 --- a/src/components/common/Avatar.jsx +++ b/src/components/common/Avatar.jsx @@ -10,7 +10,7 @@ function hashString(str) { return hash; } -const Avatar = ({ firstName, lastName, size = "sm" }) => { +const Avatar = ({ firstName, lastName, size = "sm", classAvatar }) => { // Combine firstName and lastName to create a unique string for hashing const fullName = `${firstName} ${lastName}`; @@ -51,7 +51,7 @@ const Avatar = ({ firstName, lastName, size = "sm" }) => { return (
-
+
{generateAvatarText(firstName, lastName)} diff --git a/src/pages/Directory/DirectoryListTableHeader.jsx b/src/pages/Directory/DirectoryListTableHeader.jsx index a8bc719d..fd372b3d 100644 --- a/src/pages/Directory/DirectoryListTableHeader.jsx +++ b/src/pages/Directory/DirectoryListTableHeader.jsx @@ -24,11 +24,11 @@ const DirectoryListTableHeader = ( {children, IsActive} ) => Phone
- -
+ + - Organization -
+ Organization + Category {IsActive && Action} diff --git a/src/repositories/DirectoryRepository.jsx b/src/repositories/DirectoryRepository.jsx index 9ab91633..c123cf82 100644 --- a/src/repositories/DirectoryRepository.jsx +++ b/src/repositories/DirectoryRepository.jsx @@ -6,7 +6,8 @@ export const DirectoryRepository = { GetContacts: (isActive) => api.get( `/api/directory?active=${isActive}` ), CreateContact: ( data ) => api.post( '/api/directory', data ), UpdateContact: ( id, data ) => api.put( `/api/directory/${ id }`, data ), - DeleteContact:(id)=>api.delete(`/api/directory/${id}`), + DeleteContact: ( id ) => api.delete( `/api/directory/${ id }` ), + AssignedBuckets:(id,data)=>api.post(`/api/directory/assign-bucket/${id}`,data), GetBucktes: () => api.get( `/api/directory/buckets` ), CreateBuckets: ( data ) => api.post( `/api/Directory/bucket`, data ), @@ -16,7 +17,7 @@ export const DirectoryRepository = { GetContactProfile: ( id ) => api.get( `/api/directory/profile/${ id }` ), CreateNote: ( data ) => api.post( '/api/directory/note', data ), - GetNote: ( id,isActive ) => api.get( `/api/directory/note/${ id }?active=${isActive}` ), + GetNote: ( id,isActive ) => api.get( `/api/directory/notes/${ id }?active=${isActive}` ), UpdateNote: ( id, data ) => api.put( `/api/directory/note/${ id }`, data ), DeleteNote:(id)=> api.delete(`/api/directory/note/${ id }`) } \ No newline at end of file