diff --git a/src/ModalProvider.jsx b/src/ModalProvider.jsx index bb217139..8ef00bcd 100644 --- a/src/ModalProvider.jsx +++ b/src/ModalProvider.jsx @@ -5,21 +5,24 @@ import { useAuthModal, useModal } from "./hooks/useAuth"; import SwitchTenant from "./pages/authentication/SwitchTenant"; import ChangePasswordPage from "./pages/authentication/ChangePassword"; import NewCollection from "./components/collections/ManageCollection"; +import ServiceProjectTeamAllocation from "./components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamAllocation"; const ModalProvider = () => { const { isOpen, onClose } = useOrganizationModal(); const { isOpen: isAuthOpen } = useAuthModal(); - const {isOpen:isChangePass} = useModal("ChangePassword") - const {isOpen:isCollectionNew} = useModal("newCollection"); + const { isOpen: isChangePass } = useModal("ChangePassword"); + const { isOpen: isCollectionNew } = useModal("newCollection"); + const { isOpen: isServiceTeamAllocation } = useModal("ServiceTeamAllocation"); return ( <> {isOpen && } {isAuthOpen && } - {isChangePass && } - {isCollectionNew && } + {isChangePass && } + {isCollectionNew && } + {isServiceTeamAllocation && } ); }; -export default ModalProvider; \ No newline at end of file +export default ModalProvider; diff --git a/src/components/ServiceProject/ServiceProjectTeam/ProjectTeam.jsx b/src/components/ServiceProject/ServiceProjectTeam/ProjectTeam.jsx new file mode 100644 index 00000000..6ce2a6fb --- /dev/null +++ b/src/components/ServiceProject/ServiceProjectTeam/ProjectTeam.jsx @@ -0,0 +1,20 @@ +import React from 'react' +import { useModal } from '../../../hooks/useAuth' + + +const ProjectTeam = () => { +const {onOpen} = useModal("ServiceTeamAllocation"); + + return ( +
+
+
+
+
+
+ {/* */} +
+ ) +} + +export default ProjectTeam diff --git a/src/components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamAllocation.jsx b/src/components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamAllocation.jsx new file mode 100644 index 00000000..f2ac7a84 --- /dev/null +++ b/src/components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamAllocation.jsx @@ -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 = ( +
+
+ setSelectTeam(e)} + isLoading={isLoading} + /> +
+
+ +
+
+ ); + return ( + + ); +}; + +export default ServiceProjectTeamAllocation; diff --git a/src/components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamList.jsx b/src/components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamList.jsx new file mode 100644 index 00000000..418fc093 --- /dev/null +++ b/src/components/ServiceProject/ServiceProjectTeam/ServiceProjectTeamList.jsx @@ -0,0 +1,12 @@ +import React from 'react' + +const ServiceProjectTeamList = () => { + + return ( +
+ +
+ ) +} + +export default ServiceProjectTeamList diff --git a/src/components/common/Forms/SelectFieldServerSide.jsx b/src/components/common/Forms/SelectFieldServerSide.jsx index c0746775..c2014ed6 100644 --- a/src/components/common/Forms/SelectFieldServerSide.jsx +++ b/src/components/common/Forms/SelectFieldServerSide.jsx @@ -1,44 +1,108 @@ import React, { useEffect, useRef, useState } from "react"; import Label from "../Label"; import { useDebounce } from "../../../utils/appUtils"; +import { useEmployeesName } from "../../../hooks/useEmployees"; -const SelectFieldServerSide = ({ +const SelectEmployeeServerSide = ({ label = "Select", - options = [], - placeholder = "Select Option", + placeholder = "Select Employee", required = false, - value, + value = null, onChange, valueKey = "id", - labelKey = "name", - isLoading = false, + isFullObject = false, + isMultiple = false, + projectId = null, + isAllEmployee = false, }) => { - const [searchText,setSeachText] = useState("") - const debounce = useDebounce(searchText,300); -// const {} = use + const [searchText, setSearchText] = useState(""); + const debounce = useDebounce(searchText, 300); + + const { data, isLoading } = useEmployeesName( + projectId, + debounce, + isAllEmployee + ); + + const options = data?.data ?? []; const [open, setOpen] = useState(false); 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(() => { - const handleClickOutside = (event) => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + const handleClickOutside = (e) => { + if (dropdownRef.current && !dropdownRef.current.contains(e.target)) { setOpen(false); } }; + document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); - const selectedOption = options.find((opt) => opt[valueKey] === value); - - const displayText = selectedOption ? selectedOption[labelKey] : placeholder; - + /** ----------------------------- + * HANDLE SELECT + * ----------------------------- */ const handleSelect = (option) => { - onChange(option[valueKey]); - setOpen(false); - }; + if (!isMultiple) { + // 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 (
@@ -48,22 +112,21 @@ const SelectFieldServerSide = ({ )} + {/* MAIN BUTTON */} - {open && !isLoading && ( + {/* DROPDOWN */} + {open && (
    - {options.map((option, i) => ( -
  • - -
  • - ))} +
    + setSearchText(e.target.value)} + className="form-control form-control-sm" + placeholder="Search..." + /> +
    + + {isLoading && ( +
  • Loading...
  • + )} + + {!isLoading && options.length === 0 && ( +
  • No results found
  • + )} + + {!isLoading && + options.map((option) => { + const isActive = isMultiple + ? selectedList.some((x) => x[valueKey] === option[valueKey]) + : selectedSingle && + selectedSingle[valueKey] === option[valueKey]; + + return ( +
  • + +
  • + ); + })}
)}
); }; -export default SelectFieldServerSide; +export default SelectEmployeeServerSide; diff --git a/src/components/common/Modal.jsx b/src/components/common/Modal.jsx index 876ff0e8..e8292153 100644 --- a/src/components/common/Modal.jsx +++ b/src/components/common/Modal.jsx @@ -40,7 +40,7 @@ const Modal = ({ {/* Body */} -
{body}
+
{body}
diff --git a/src/hooks/masterHook/useMaster.js b/src/hooks/masterHook/useMaster.js index 5e786adb..978bf30e 100644 --- a/src/hooks/masterHook/useMaster.js +++ b/src/hooks/masterHook/useMaster.js @@ -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== const fetchMasterData = async (masterType) => { switch (masterType) { @@ -385,6 +404,8 @@ const useMaster = () => { export default useMaster; //#endregion + + // ================================Mutation==================================== //#region Job Role @@ -435,6 +456,9 @@ export const useCreateJobRole = (onSuccessCallback) => { //#endregion Job Role + + + //#region Application Role export const useCreateApplicationRole = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -480,6 +504,10 @@ export const useUpdateApplicationRole = (onSuccessCallback) => { }; //#endregion + + + + //#region Create work Category export const useCreateWorkCategory = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -525,6 +553,11 @@ export const useUpdateWorkCategory = (onSuccessCallback) => { }; //#endregion + + + + + //#region Contact Category export const useCreateContactCategory = (onSuccessCallback) => { @@ -575,6 +608,10 @@ export const useUpdateContactCategory = (onSuccessCallback) => { //#endregion + + + + //#region Contact Tag export const useCreateContactTag = (onSuccessCallback) => { @@ -621,6 +658,10 @@ export const useUpdateContactTag = (onSuccessCallback) => { }; //#endregion + + + + //#region Expense Category export const useCreateExpenseCategory = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -666,6 +707,11 @@ export const useUpdateExpenseCategory = (onSuccessCallback) => { //#endregion + + + + + //#region Payment Mode export const useCreatePaymentMode = (onSuccessCallback) => { @@ -712,6 +758,10 @@ export const useUpdatePaymentMode = (onSuccessCallback) => { //#endregion + + + + // Services------------------------------- // export const useCreateService = (onSuccessCallback) => { @@ -793,6 +843,10 @@ export const useUpdateService = (onSuccessCallback) => { //#endregion + + + + //#region Activity Grouph export const useCreateActivityGroup = (onSuccessCallback) => { @@ -857,6 +911,10 @@ export const useUpdateActivityGroup = (onSuccessCallback) => { //#endregion + + + + //#region Activities export const useCreateActivity = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -911,6 +969,11 @@ export const useUpdateActivity = (onSuccessCallback) => { //#endregion + + + + + //#region Expense Status export const useCreateExpenseStatus = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -954,6 +1017,10 @@ export const useUpdateExpenseStatus = (onSuccessCallback) => { }; //#endregion + + + + //#region Document-Category export const useCreateDocumentCatgory = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -1000,6 +1067,11 @@ export const useUpdateDocumentCategory = (onSuccessCallback) => { }; //#endregion + + + + + //#region Document-Type export const useCreateDocumentType = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -1044,6 +1116,10 @@ export const useUpdateDocumentType = (onSuccessCallback) => { }; //#endregion + + + + //#region Payment Adjustment Head export const useCreatePaymentAjustmentHead = (onSuccessCallback) => { const queryClient = useQueryClient(); @@ -1087,6 +1163,10 @@ export const useUpdatePaymentAjustmentHead = (onSuccessCallback) => { }; //#endregion + + + + //#region ==Delete Master== export const useDeleteMasterItem = () => { const queryClient = useQueryClient(); @@ -1122,6 +1202,8 @@ export const useDeleteMasterItem = () => { //#endregion + + export const useDeleteServiceGroup = () => { const queryClient = useQueryClient(); diff --git a/src/hooks/useServiceProject.jsx b/src/hooks/useServiceProject.jsx index 8cff3238..a8fd799e 100644 --- a/src/hooks/useServiceProject.jsx +++ b/src/hooks/useServiceProject.jsx @@ -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 //#region Service Jobs diff --git a/src/pages/ServiceProject/ServiceProjectDetail.jsx b/src/pages/ServiceProject/ServiceProjectDetail.jsx index 8708970f..ac5eecc5 100644 --- a/src/pages/ServiceProject/ServiceProjectDetail.jsx +++ b/src/pages/ServiceProject/ServiceProjectDetail.jsx @@ -4,6 +4,7 @@ import ServiceProjectNav from "../../components/ServiceProject/ServiceProjectNav import { ComingSoonPage } from "../Misc/ComingSoonPage"; import ServiceProjectProfile from "../../components/ServiceProject/ServiceProjectProfile"; import Jobs from "../../components/ServiceProject/Jobs"; +import ProjectTeam from "../../components/ServiceProject/ServiceProjectTeam/ProjectTeam"; const ServiceProjectDetail = () => { const [activePill, setActivePill] = useState( @@ -17,6 +18,8 @@ const ServiceProjectDetail = () => { switch (activePill) { case "profile": return ; + case "teams": + return ; case "jobs": return ; default: diff --git a/src/repositories/MastersRepository.jsx b/src/repositories/MastersRepository.jsx index 9542300d..72cb91d9 100644 --- a/src/repositories/MastersRepository.jsx +++ b/src/repositories/MastersRepository.jsx @@ -149,5 +149,9 @@ export const MasterRespository = { 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`), }; diff --git a/src/repositories/ServiceProjectRepository.jsx b/src/repositories/ServiceProjectRepository.jsx index 4632e86b..20b5aa8e 100644 --- a/src/repositories/ServiceProjectRepository.jsx +++ b/src/repositories/ServiceProjectRepository.jsx @@ -11,7 +11,7 @@ export const ServiceProjectRepository = { api.put(`/api/ServiceProject/edit/${id}`, data), DeleteServiceProject: (id, isActive = false) => api.delete(`/api/ServiceProject/delete/${id}?isActive=${isActive}`), - + AllocateEmployee: (data) => api.post(`/api/ServiceProject/manage/allocation`), //#region Job CreateJob: (data) => api.post(`/api/ServiceProject/job/create`, data),