add new EmployeeSearch for multiple

This commit is contained in:
pramod.mahajan 2025-11-14 20:07:28 +05:30
parent 92bcfb73c6
commit 7e646ca3e8
11 changed files with 331 additions and 48 deletions

View File

@ -5,12 +5,14 @@ import { useAuthModal, useModal } from "./hooks/useAuth";
import SwitchTenant from "./pages/authentication/SwitchTenant"; import SwitchTenant from "./pages/authentication/SwitchTenant";
import ChangePasswordPage from "./pages/authentication/ChangePassword"; import ChangePasswordPage from "./pages/authentication/ChangePassword";
import NewCollection from "./components/collections/ManageCollection"; import NewCollection from "./components/collections/ManageCollection";
import ServiceProjectTeamAllocation from "./components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamAllocation";
const ModalProvider = () => { const ModalProvider = () => {
const { isOpen, onClose } = useOrganizationModal(); const { isOpen, onClose } = useOrganizationModal();
const { isOpen: isAuthOpen } = useAuthModal(); const { isOpen: isAuthOpen } = useAuthModal();
const {isOpen:isChangePass} = useModal("ChangePassword") const { isOpen: isChangePass } = useModal("ChangePassword");
const { isOpen: isCollectionNew } = useModal("newCollection"); const { isOpen: isCollectionNew } = useModal("newCollection");
const { isOpen: isServiceTeamAllocation } = useModal("ServiceTeamAllocation");
return ( return (
<> <>
@ -18,6 +20,7 @@ const ModalProvider = () => {
{isAuthOpen && <SwitchTenant />} {isAuthOpen && <SwitchTenant />}
{isChangePass && <ChangePasswordPage />} {isChangePass && <ChangePasswordPage />}
{isCollectionNew && <NewCollection />} {isCollectionNew && <NewCollection />}
{isServiceTeamAllocation && <ServiceProjectTeamAllocation />}
</> </>
); );
}; };

View File

@ -0,0 +1,20 @@
import React from 'react'
import { useModal } from '../../../hooks/useAuth'
const ProjectTeam = () => {
const {onOpen} = useModal("ServiceTeamAllocation");
return (
<div className='card page-min-h mt-2'>
<div className='row text-end'>
<div className='col-12'>
<div className='p-2'><button onClick={onOpen} className='btn btn-sm btn-primary'><i className='bx bx-plus-circle me-2'></i>Assign Employee</button></div>
</div>
</div>
{/* <ServiceProjectTeamList/> */}
</div>
)
}
export default ProjectTeam

View File

@ -0,0 +1,52 @@
import React, { useState } from "react";
import { useModal } from "../../../hooks/useAuth";
import Modal from "../../common/Modal";
import Label from "../../common/Label";
import { useTeamRole } from "../../../hooks/masterHook/useMaster";
import SelectField from "../../common/Forms/SelectField";
import SelectFieldServerSide from "../../common/Forms/SelectFieldServerSide";
import SelectEmployeeServerSide from "../../common/Forms/SelectFieldServerSide";
const ServiceProjectTeamAllocation = () => {
const { isOpen, onClose } = useModal("ServiceTeamAllocation");
const [selectedTeam, setSelectTeam] = useState(null);
const [selectedEmployees, setSelectedEmployees] = useState(null);
const { data, isLoading, isError, error } = useTeamRole();
console.log(selectedEmployees);
const TeamAllocation = (
<div className="row text-start">
<div className="col-6">
<SelectField
label="Select Team"
required
options={data}
value={selectedTeam}
labelKey="name"
valueKey="id"
valueKey="name"
onChange={(e) => setSelectTeam(e)}
isLoading={isLoading}
/>
</div>
<div className="col-6">
<SelectEmployeeServerSide
isMultiple={true}
isFullObject={true}
value={selectedEmployees}
onChange={setSelectedEmployees}
/>
</div>
</div>
);
return (
<Modal
size="lg"
title={"Team Allocation"}
isOpen={isOpen}
onClose={onClose}
body={TeamAllocation}
/>
);
};
export default ServiceProjectTeamAllocation;

View File

@ -0,0 +1,12 @@
import React from 'react'
const ServiceProjectTeamList = () => {
return (
<div>
</div>
)
}
export default ServiceProjectTeamList

View File

@ -1,44 +1,108 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import Label from "../Label"; import Label from "../Label";
import { useDebounce } from "../../../utils/appUtils"; import { useDebounce } from "../../../utils/appUtils";
import { useEmployeesName } from "../../../hooks/useEmployees";
const SelectFieldServerSide = ({ const SelectEmployeeServerSide = ({
label = "Select", label = "Select",
options = [], placeholder = "Select Employee",
placeholder = "Select Option",
required = false, required = false,
value, value = null,
onChange, onChange,
valueKey = "id", valueKey = "id",
labelKey = "name", isFullObject = false,
isLoading = false, isMultiple = false,
projectId = null,
isAllEmployee = false,
}) => { }) => {
const [searchText,setSeachText] = useState("") const [searchText, setSearchText] = useState("");
const debounce = useDebounce(searchText, 300); const debounce = useDebounce(searchText, 300);
// const {} = use
const { data, isLoading } = useEmployeesName(
projectId,
debounce,
isAllEmployee
);
const options = data?.data ?? [];
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const dropdownRef = useRef(null); const dropdownRef = useRef(null);
const getDisplayName = (emp) => {
if (!emp) return "";
return `${emp.firstName || ""} ${emp.lastName || ""}`.trim();
};
/** -----------------------------
* SELECTED OPTION (SINGLE)
* ----------------------------- */
let selectedSingle = null;
if (!isMultiple) {
if (isFullObject && value) selectedSingle = value;
else if (!isFullObject && value)
selectedSingle = options.find((o) => o[valueKey] === value);
}
/** -----------------------------
* SELECTED OPTION (MULTIPLE)
* ----------------------------- */
let selectedList = [];
if (isMultiple && Array.isArray(value)) {
if (isFullObject) selectedList = value;
else {
selectedList = options.filter((opt) => value.includes(opt[valueKey]));
}
}
/** Main button label */
const displayText = !isMultiple
? getDisplayName(selectedSingle) || placeholder
: selectedList.length > 0
? selectedList.map((e) => getDisplayName(e)).join(", ")
: placeholder;
/** -----------------------------
* HANDLE OUTSIDE CLICK
* ----------------------------- */
useEffect(() => { useEffect(() => {
const handleClickOutside = (event) => { const handleClickOutside = (e) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
setOpen(false); setOpen(false);
} }
}; };
document.addEventListener("mousedown", handleClickOutside); document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside);
}, []); }, []);
const selectedOption = options.find((opt) => opt[valueKey] === value); /** -----------------------------
* HANDLE SELECT
const displayText = selectedOption ? selectedOption[labelKey] : placeholder; * ----------------------------- */
const handleSelect = (option) => { const handleSelect = (option) => {
onChange(option[valueKey]); if (!isMultiple) {
setOpen(false); // SINGLE SELECT
}; if (isFullObject) onChange(option);
else onChange(option[valueKey]);
} else {
// MULTIPLE SELECT
let updated = [];
const toggleDropdown = () => setOpen((prev) => !prev); const exists = selectedList.some((e) => e[valueKey] === option[valueKey]);
if (exists) {
// remove
updated = selectedList.filter((e) => e[valueKey] !== option[valueKey]);
} else {
// add
updated = [...selectedList, option];
}
if (isFullObject) onChange(updated);
else onChange(updated.map((x) => x[valueKey]));
}
};
return ( return (
<div className="mb-3 position-relative" ref={dropdownRef}> <div className="mb-3 position-relative" ref={dropdownRef}>
@ -48,22 +112,21 @@ const SelectFieldServerSide = ({
</Label> </Label>
)} )}
{/* MAIN BUTTON */}
<button <button
type="button" type="button"
className={`select2-icons form-select d-flex align-items-center justify-content-between ${ className={`select2-icons form-select d-flex align-items-center justify-content-between ${
open ? "show" : "" open ? "show" : ""
}`} }`}
onClick={toggleDropdown} onClick={() => setOpen((prev) => !prev)}
disabled={isLoading}
> >
<span <span className={`text-truncate ${!displayText ? "text-muted" : ""}`}>
className={`text-truncate ${!selectedOption ? "text-muted" : ""}`} {displayText}
>
{isLoading ? "Loading..." : displayText}
</span> </span>
</button> </button>
{open && !isLoading && ( {/* DROPDOWN */}
{open && (
<ul <ul
className="dropdown-menu w-100 shadow-sm show animate__fadeIn" className="dropdown-menu w-100 shadow-sm show animate__fadeIn"
style={{ style={{
@ -76,23 +139,47 @@ const SelectFieldServerSide = ({
overflow: "hidden", overflow: "hidden",
}} }}
> >
{options.map((option, i) => ( <div className="p-1">
<li key={i}> <input
type="search"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
className="form-control form-control-sm"
placeholder="Search..."
/>
</div>
{isLoading && (
<li className="dropdown-item text-muted">Loading...</li>
)}
{!isLoading && options.length === 0 && (
<li className="dropdown-item text-muted">No results found</li>
)}
{!isLoading &&
options.map((option) => {
const isActive = isMultiple
? selectedList.some((x) => x[valueKey] === option[valueKey])
: selectedSingle &&
selectedSingle[valueKey] === option[valueKey];
return (
<li key={option[valueKey]}>
<button <button
type="button" type="button"
className={`dropdown-item ${ className={`dropdown-item ${isActive ? "active" : ""}`}
option[valueKey] === value ? "active" : ""
}`}
onClick={() => handleSelect(option)} onClick={() => handleSelect(option)}
> >
{option[labelKey]} {getDisplayName(option)}
</button> </button>
</li> </li>
))} );
})}
</ul> </ul>
)} )}
</div> </div>
); );
}; };
export default SelectFieldServerSide; export default SelectEmployeeServerSide;

View File

@ -40,7 +40,7 @@ const Modal = ({
</div> </div>
{/* Body */} {/* Body */}
<div className="modal-body pt-0">{body}</div> <div className="modal-body pt-0 text-black">{body}</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -296,6 +296,25 @@ export const useOrganizationType = () => {
}); });
}; };
export const useJobStatus=()=>{
return useQuery({
queryKey:["Job_Staus"],
queryFn:async()=>{
const resp = await MasterRespository.getJobStatus();
return resp.data;
}
})
}
export const useTeamRole=()=>{
return useQuery({
queryKey:["Team_Role"],
queryFn:async()=>{
const resp = await MasterRespository.getTeamRole();
return resp.data;
}
})
}
//#region ==Get Masters== //#region ==Get Masters==
const fetchMasterData = async (masterType) => { const fetchMasterData = async (masterType) => {
switch (masterType) { switch (masterType) {
@ -385,6 +404,8 @@ const useMaster = () => {
export default useMaster; export default useMaster;
//#endregion //#endregion
// ================================Mutation==================================== // ================================Mutation====================================
//#region Job Role //#region Job Role
@ -435,6 +456,9 @@ export const useCreateJobRole = (onSuccessCallback) => {
//#endregion Job Role //#endregion Job Role
//#region Application Role //#region Application Role
export const useCreateApplicationRole = (onSuccessCallback) => { export const useCreateApplicationRole = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -480,6 +504,10 @@ export const useUpdateApplicationRole = (onSuccessCallback) => {
}; };
//#endregion //#endregion
//#region Create work Category //#region Create work Category
export const useCreateWorkCategory = (onSuccessCallback) => { export const useCreateWorkCategory = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -525,6 +553,11 @@ export const useUpdateWorkCategory = (onSuccessCallback) => {
}; };
//#endregion //#endregion
//#region Contact Category //#region Contact Category
export const useCreateContactCategory = (onSuccessCallback) => { export const useCreateContactCategory = (onSuccessCallback) => {
@ -575,6 +608,10 @@ export const useUpdateContactCategory = (onSuccessCallback) => {
//#endregion //#endregion
//#region Contact Tag //#region Contact Tag
export const useCreateContactTag = (onSuccessCallback) => { export const useCreateContactTag = (onSuccessCallback) => {
@ -621,6 +658,10 @@ export const useUpdateContactTag = (onSuccessCallback) => {
}; };
//#endregion //#endregion
//#region Expense Category //#region Expense Category
export const useCreateExpenseCategory = (onSuccessCallback) => { export const useCreateExpenseCategory = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -666,6 +707,11 @@ export const useUpdateExpenseCategory = (onSuccessCallback) => {
//#endregion //#endregion
//#region Payment Mode //#region Payment Mode
export const useCreatePaymentMode = (onSuccessCallback) => { export const useCreatePaymentMode = (onSuccessCallback) => {
@ -712,6 +758,10 @@ export const useUpdatePaymentMode = (onSuccessCallback) => {
//#endregion //#endregion
// Services------------------------------- // Services-------------------------------
// export const useCreateService = (onSuccessCallback) => { // export const useCreateService = (onSuccessCallback) => {
@ -793,6 +843,10 @@ export const useUpdateService = (onSuccessCallback) => {
//#endregion //#endregion
//#region Activity Grouph //#region Activity Grouph
export const useCreateActivityGroup = (onSuccessCallback) => { export const useCreateActivityGroup = (onSuccessCallback) => {
@ -857,6 +911,10 @@ export const useUpdateActivityGroup = (onSuccessCallback) => {
//#endregion //#endregion
//#region Activities //#region Activities
export const useCreateActivity = (onSuccessCallback) => { export const useCreateActivity = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -911,6 +969,11 @@ export const useUpdateActivity = (onSuccessCallback) => {
//#endregion //#endregion
//#region Expense Status //#region Expense Status
export const useCreateExpenseStatus = (onSuccessCallback) => { export const useCreateExpenseStatus = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -954,6 +1017,10 @@ export const useUpdateExpenseStatus = (onSuccessCallback) => {
}; };
//#endregion //#endregion
//#region Document-Category //#region Document-Category
export const useCreateDocumentCatgory = (onSuccessCallback) => { export const useCreateDocumentCatgory = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -1000,6 +1067,11 @@ export const useUpdateDocumentCategory = (onSuccessCallback) => {
}; };
//#endregion //#endregion
//#region Document-Type //#region Document-Type
export const useCreateDocumentType = (onSuccessCallback) => { export const useCreateDocumentType = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -1044,6 +1116,10 @@ export const useUpdateDocumentType = (onSuccessCallback) => {
}; };
//#endregion //#endregion
//#region Payment Adjustment Head //#region Payment Adjustment Head
export const useCreatePaymentAjustmentHead = (onSuccessCallback) => { export const useCreatePaymentAjustmentHead = (onSuccessCallback) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -1087,6 +1163,10 @@ export const useUpdatePaymentAjustmentHead = (onSuccessCallback) => {
}; };
//#endregion //#endregion
//#region ==Delete Master== //#region ==Delete Master==
export const useDeleteMasterItem = () => { export const useDeleteMasterItem = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -1122,6 +1202,8 @@ export const useDeleteMasterItem = () => {
//#endregion //#endregion
export const useDeleteServiceGroup = () => { export const useDeleteServiceGroup = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();

View File

@ -103,6 +103,26 @@ export const useActiveInActiveServiceProject = (onSuccessCallback) => {
}, },
}); });
}; };
export const useAllocationTeam=()=>{
const queryClient = useQueryClient();
return useMutation({
mutationFn:async(paylaod)=> await ServiceProjectRepository.AllocateEmployee(paylaod),
onSuccess: (data, variables) => {
// queryClient.invalidateQueries({ queryKey: ["serviceProjects"] });
if (onSuccessCallback) onSuccessCallback();
showToast(`${variables?.length} employee allocated successfully`, "success");
},
onError: (error) => {
showToast(
error?.response?.data?.message ||
error.message ||
"Failed to update project",
"error"
);
},
})
}
//#endregion //#endregion
//#region Service Jobs //#region Service Jobs

View File

@ -4,6 +4,7 @@ import ServiceProjectNav from "../../components/ServiceProject/ServiceProjectNav
import { ComingSoonPage } from "../Misc/ComingSoonPage"; import { ComingSoonPage } from "../Misc/ComingSoonPage";
import ServiceProjectProfile from "../../components/ServiceProject/ServiceProjectProfile"; import ServiceProjectProfile from "../../components/ServiceProject/ServiceProjectProfile";
import Jobs from "../../components/ServiceProject/Jobs"; import Jobs from "../../components/ServiceProject/Jobs";
import ProjectTeam from "../../components/ServiceProject/ServiceProjectTeam/ProjectTeam";
const ServiceProjectDetail = () => { const ServiceProjectDetail = () => {
const [activePill, setActivePill] = useState( const [activePill, setActivePill] = useState(
@ -17,6 +18,8 @@ const ServiceProjectDetail = () => {
switch (activePill) { switch (activePill) {
case "profile": case "profile":
return <ServiceProjectProfile />; return <ServiceProjectProfile />;
case "teams":
return <ProjectTeam />;
case "jobs": case "jobs":
return <Jobs />; return <Jobs />;
default: default:

View File

@ -149,5 +149,9 @@ export const MasterRespository = {
getCurrencies: () => api.get(`/api/Master/currencies/list`), getCurrencies: () => api.get(`/api/Master/currencies/list`),
getRecurringStatus:()=>api.get(`/api/Master/recurring-status/list`) getRecurringStatus: () => api.get(`/api/Master/recurring-status/list`),
getJobStatus: () => api.get("/api/Master/job-status/list"),
getTeamRole:()=> api.get(`/api/Master/team-roles/list`),
}; };

View File

@ -11,7 +11,7 @@ export const ServiceProjectRepository = {
api.put(`/api/ServiceProject/edit/${id}`, data), api.put(`/api/ServiceProject/edit/${id}`, data),
DeleteServiceProject: (id, isActive = false) => DeleteServiceProject: (id, isActive = false) =>
api.delete(`/api/ServiceProject/delete/${id}?isActive=${isActive}`), api.delete(`/api/ServiceProject/delete/${id}?isActive=${isActive}`),
AllocateEmployee: (data) => api.post(`/api/ServiceProject/manage/allocation`),
//#region Job //#region Job
CreateJob: (data) => api.post(`/api/ServiceProject/job/create`, data), CreateJob: (data) => api.post(`/api/ServiceProject/job/create`, data),