Merge branch 'Service_Project_Managment' of https://git.marcoaiot.com/admin/marco.pms.web into Service_Project_Management_Bugs
This commit is contained in:
commit
85b81b35da
@ -4,8 +4,8 @@ import { useServiceProject } from "../../hooks/useServiceProject";
|
||||
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||
|
||||
const ServiceProjectProfile = () => {
|
||||
const { id } = useParams();
|
||||
const { data, isLoading, isError, error } = useServiceProject(id);
|
||||
const { projectId } = useParams();
|
||||
const { data, isLoading, isError, error } = useServiceProject(projectId);
|
||||
if (isLoading) {
|
||||
return <div className="">Loadng.</div>;
|
||||
}
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
import React from 'react'
|
||||
import { useModal } from '../../../hooks/useAuth'
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
|
||||
const ProjectTeam = () => {
|
||||
const {onOpen} = useModal("ServiceTeamAllocation");
|
||||
const {projectId}= useParams();
|
||||
|
||||
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 className='p-2'><button onClick={()=>onOpen({projectId:projectId})} className='btn btn-sm btn-primary'><i className='bx bx-plus-circle me-2'></i>Assign Employee</button></div>
|
||||
</div>
|
||||
</div>
|
||||
{/* <ServiceProjectTeamList/> */}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useModal } from "../../../hooks/useAuth";
|
||||
import Modal from "../../common/Modal";
|
||||
import Label from "../../common/Label";
|
||||
@ -6,45 +6,233 @@ import { useTeamRole } from "../../../hooks/masterHook/useMaster";
|
||||
import SelectField from "../../common/Forms/SelectField";
|
||||
import SelectFieldServerSide from "../../common/Forms/SelectFieldServerSide";
|
||||
import SelectEmployeeServerSide from "../../common/Forms/SelectFieldServerSide";
|
||||
import Avatar from "../../common/Avatar";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { EmployeeChip } from "../../common/Chips";
|
||||
import {
|
||||
useAllocationServiceProjectTeam,
|
||||
useServiceProjectTeam,
|
||||
} from "../../../hooks/useServiceProject";
|
||||
import { SpinnerLoader } from "../../common/Loader";
|
||||
import showToast from "../../../services/toastService";
|
||||
|
||||
const ServiceProjectTeamAllocation = () => {
|
||||
const { isOpen, onClose } = useModal("ServiceTeamAllocation");
|
||||
const { isOpen, onClose, data: Project } = useModal("ServiceTeamAllocation");
|
||||
const [deletingId, setSeletingId] = useState(null);
|
||||
const {
|
||||
data: Team,
|
||||
isLoading: isTeamLoading,
|
||||
isError: isTeamError,
|
||||
error: teamError,
|
||||
} = useServiceProjectTeam(Project?.projectId, true);
|
||||
const [isManageEmployee, setIsMenageEmployee] = useState(false);
|
||||
const [nonDuplicateEmployee, setNonDuplicateEmployee] = useState([]);
|
||||
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}
|
||||
/>
|
||||
const [selectedEmployees, setSelectedEmployees] = useState([]);
|
||||
const { data, isLoading } = useTeamRole();
|
||||
|
||||
const TeamAllocationColumns = [
|
||||
{
|
||||
key: "team",
|
||||
label: "Role",
|
||||
getValue: (e) => e?.teamName || "-",
|
||||
},
|
||||
];
|
||||
const handleRemove = (id) => {
|
||||
setSelectedEmployees((prev) => prev.filter((e) => e.id !== id));
|
||||
};
|
||||
const { mutate: AllocationTeam, isPending } = useAllocationServiceProjectTeam(
|
||||
() => {
|
||||
setSelectedEmployees([]);
|
||||
setSeletingId(null);
|
||||
}
|
||||
);
|
||||
|
||||
const AssignEmployee = () => {
|
||||
let payload = selectedEmployees.map((e) => {
|
||||
return {
|
||||
projectId: Project?.projectId,
|
||||
employeeId: e.id,
|
||||
teamRoleId: selectedTeam,
|
||||
isActive: true,
|
||||
};
|
||||
});
|
||||
AllocationTeam({ payload, isActive: true });
|
||||
};
|
||||
|
||||
const handleDeAllocation = (emp) => {
|
||||
setSeletingId(emp?.id);
|
||||
let payload = [
|
||||
{
|
||||
projectId: Project?.projectId,
|
||||
employeeId: emp.employee.id,
|
||||
teamRoleId: emp.teamRole.id,
|
||||
isActive: false,
|
||||
},
|
||||
];
|
||||
|
||||
AllocationTeam({payload:payload,isActive:false});
|
||||
};
|
||||
useEffect(() => {
|
||||
if(selectedEmployees?.length > 0 && !selectedTeam){
|
||||
handleRemove(selectedEmployees[0]?.id)
|
||||
showToast(
|
||||
`Please select a first role`,
|
||||
"warning"
|
||||
);
|
||||
}
|
||||
if (Team?.length > 0 && selectedEmployees?.length > 0) {
|
||||
setNonDuplicateEmployee((prev) => {
|
||||
let updated = [...prev];
|
||||
|
||||
selectedEmployees.forEach((selectedEmployee) => {
|
||||
const alreadyAllocated = Team.some(
|
||||
(e) =>
|
||||
e?.employee?.id === selectedEmployee?.id &&
|
||||
e?.teamRole?.id === selectedTeam
|
||||
);
|
||||
if (alreadyAllocated) {
|
||||
handleRemove(selectedEmployee?.id);
|
||||
showToast(
|
||||
`${selectedEmployee.firstName} ${selectedEmployee.lastName} already allocated with same role`,
|
||||
"warning"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return updated;
|
||||
});
|
||||
}
|
||||
}, [Team, selectedEmployees, selectedTeam]);
|
||||
|
||||
const TeamAllocationBody = (
|
||||
<div className=" text-start">
|
||||
<div className="row">
|
||||
<div className="d-flex justify-content-end">
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={() => setIsMenageEmployee(!isManageEmployee)}
|
||||
>
|
||||
Manage Employee
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{isManageEmployee && (
|
||||
<>
|
||||
<div className="col-12 col-md-6 mb-3">
|
||||
<SelectField
|
||||
label="Select Role"
|
||||
required
|
||||
options={data}
|
||||
value={selectedTeam}
|
||||
labelKey="name"
|
||||
valueKey="id"
|
||||
onChange={(e) => {
|
||||
setSelectedEmployees([]);
|
||||
setSelectTeam(e);
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-12 col-md-6 mb-3">
|
||||
<SelectEmployeeServerSide
|
||||
isMultiple={true}
|
||||
isFullObject={true}
|
||||
value={selectedEmployees}
|
||||
onChange={setSelectedEmployees}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 d-flex flex-row gap-2 flex-wrap">
|
||||
{selectedEmployees.map((e) => (
|
||||
<EmployeeChip handleRemove={handleRemove} employee={e} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<SelectEmployeeServerSide
|
||||
isMultiple={true}
|
||||
isFullObject={true}
|
||||
value={selectedEmployees}
|
||||
onChange={setSelectedEmployees}
|
||||
/>
|
||||
{(selectedEmployees?.length > 0 && isManageEmployee ) && (
|
||||
<div className="d-flex justify-content-end">
|
||||
{" "}
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
disabled={isPending}
|
||||
onClick={AssignEmployee}
|
||||
>
|
||||
{isPending && !deletingId ? "Please wait..." : "Save"}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="col-12">
|
||||
<table className="table text-center ">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Role</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{Team?.length > 0 ? (
|
||||
Team?.map((emp) => (
|
||||
<tr key={emp?.id}>
|
||||
<td className="w-min">
|
||||
<div className="d-flex align-items-center ">
|
||||
{" "}
|
||||
<Avatar
|
||||
size="xs"
|
||||
firstName={emp?.employee?.firstName}
|
||||
lastName={emp?.employee?.lastName}
|
||||
/>
|
||||
<span className="fw-medium">{`${emp?.employee?.firstName} ${emp?.employee?.lastName}`}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{emp?.teamRole?.name}</td>
|
||||
<td className="">
|
||||
{deletingId === emp.id ? (
|
||||
<div className="spinner-border spinner-border-sm "></div>
|
||||
) : (
|
||||
<span disabled={isPending}>
|
||||
<i
|
||||
className="bx bx-trash bx-sm text-danger cursor-pointer"
|
||||
onClick={() => handleDeAllocation(emp)}
|
||||
></i>
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td
|
||||
colSpan={3}
|
||||
className="text-muted border-0 d-flex justify-content-center "
|
||||
>
|
||||
{isTeamLoading ? (
|
||||
<SpinnerLoader />
|
||||
) : (
|
||||
<div className="bg-light-secondary py-3 w-50 m-auto my-2">
|
||||
<i className="bx bx-box bx-md text-primary"></i>
|
||||
<p className="fw-medium mt-3"> Please Add a Employee </p>
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
size="lg"
|
||||
title={"Team Allocation"}
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
body={TeamAllocation}
|
||||
body={TeamAllocationBody}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
45
src/components/common/Chips.jsx
Normal file
45
src/components/common/Chips.jsx
Normal file
@ -0,0 +1,45 @@
|
||||
import React from 'react'
|
||||
|
||||
export const EmployeeChip = ({handleRemove,employee}) => {
|
||||
return(
|
||||
<span
|
||||
key={employee?.id}
|
||||
className="tagify__tag d-inline-flex align-items-center me-1 mb-1"
|
||||
role="listitem"
|
||||
>
|
||||
<div className="d-flex align-items-center">
|
||||
{employee?.photo ? (
|
||||
<span className="tagify__tag__avatar-wrap me-1">
|
||||
<img
|
||||
src={employee?.avataremployeerl || "/defaemployeelt-avatar.png"}
|
||||
alt={`${employee?.firstName || ""} ${employee?.lastName || ""}`}
|
||||
style={{ width: 12, height: 12, objectFit: "cover" }}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
<div className="avatar avatar-xs me-2">
|
||||
<span className="avatar-initial roemployeended-circle bg-label-secondary">
|
||||
{employee?.firstName?.[0] || ""}
|
||||
{employee?.lastName?.[0] || ""}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="d-flex flex-colemployeemn">
|
||||
<span className="tagify__tag-text">
|
||||
{employee?.firstName} {employee?.lastName}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<bemployeetton
|
||||
type="bemployeetton"
|
||||
className="tagify__tag__removeBtn border-none"
|
||||
onClick={() => handleRemove(employee?.id)}
|
||||
aria-label={`Remove ${employee?.firstName}`}
|
||||
title="Remove"
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@ -128,7 +128,7 @@ const SelectEmployeeServerSide = ({
|
||||
{/* DROPDOWN */}
|
||||
{open && (
|
||||
<ul
|
||||
className="dropdown-menu w-100 shadow-sm show animate__fadeIn"
|
||||
className="dropdown-menu w-100 shadow-sm show animate__fadeIn h-64 overflow-auto rounded"
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: "100%",
|
||||
|
||||
@ -25,16 +25,24 @@ import showToast from "../services/toastService";
|
||||
|
||||
export const useModal = (modalType) => {
|
||||
const dispatch = useDispatch();
|
||||
const isOpen = useSelector(
|
||||
(state) => state.localVariables.modals[modalType]?.isOpen
|
||||
|
||||
const modalState = useSelector(
|
||||
(state) => state.localVariables.modals[modalType] || {}
|
||||
);
|
||||
|
||||
const onOpen = (data = {}) => dispatch(openModal({ modalType, data }));
|
||||
const { isOpen = false, data = null } = modalState;
|
||||
|
||||
const onOpen = (payload = {}) =>
|
||||
dispatch(openModal({ modalType, data: payload }));
|
||||
|
||||
const onClose = () => dispatch(closeModal({ modalType }));
|
||||
|
||||
const onToggle = () => dispatch(toggleModal({ modalType }));
|
||||
|
||||
return { isOpen, onOpen, onClose, onToggle };
|
||||
return { isOpen, data, onOpen, onClose, onToggle };
|
||||
};
|
||||
|
||||
|
||||
export const useSubscription = (frequency) => {
|
||||
return useQuery({
|
||||
queryKey: ["subscriptionPlans", frequency],
|
||||
|
||||
@ -32,6 +32,20 @@ export const useServiceProject = (projectId) => {
|
||||
enabled: !!projectId,
|
||||
});
|
||||
};
|
||||
|
||||
export const useServiceProjectTeam = (projectId, isActive) => {
|
||||
return useQuery({
|
||||
queryKey: ["serviceProjectTeam", projectId, isActive],
|
||||
queryFn: async () => {
|
||||
const response = await ServiceProjectRepository.GetAllocatedEmployees(
|
||||
projectId,
|
||||
isActive
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
enabled: !!projectId,
|
||||
});
|
||||
};
|
||||
export const useCreateServiceProject = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
@ -104,14 +118,22 @@ export const useActiveInActiveServiceProject = (onSuccessCallback) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const useAllocationTeam=()=>{
|
||||
export const useAllocationServiceProjectTeam = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn:async(paylaod)=> await ServiceProjectRepository.AllocateEmployee(paylaod),
|
||||
onSuccess: (data, variables) => {
|
||||
// queryClient.invalidateQueries({ queryKey: ["serviceProjects"] });
|
||||
mutationFn: async ({ payload, isActive }) =>
|
||||
await ServiceProjectRepository.AllocateEmployee(payload),
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: ["serviceProjectTeam"] });
|
||||
if (onSuccessCallback) onSuccessCallback();
|
||||
showToast(`${variables?.length} employee allocated successfully`, "success");
|
||||
if (variables.isActive) {
|
||||
showToast(
|
||||
`${variables?.payload.length} Employee allocated successfully`,
|
||||
"success"
|
||||
);
|
||||
} else {
|
||||
showToast(`Employee DeAllocated successfully`, "success");
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
showToast(
|
||||
@ -121,8 +143,9 @@ export const useAllocationTeam=()=>{
|
||||
"error"
|
||||
);
|
||||
},
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Service Jobs
|
||||
|
||||
@ -11,7 +11,12 @@ 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`),
|
||||
AllocateEmployee: (data) =>
|
||||
api.post(`/api/ServiceProject/manage/allocation`, data),
|
||||
GetAllocatedEmployees: (projectId, isActive) =>
|
||||
api.get(
|
||||
`/api/ServiceProject/get/allocation/list?projectId=${projectId}&isActive=${isActive} `
|
||||
),
|
||||
//#region Job
|
||||
|
||||
CreateJob: (data) => api.post(`/api/ServiceProject/job/create`, data),
|
||||
|
||||
@ -95,7 +95,7 @@ const router = createBrowserRouter(
|
||||
{ path: "/projects", element: <ProjectPage /> },
|
||||
{ path: "/projects/details", element: <ProjectDetails /> },
|
||||
{ path: "/project/manage/:projectId", element: <ManageProject /> },
|
||||
{ path: "service-projects/:id", element: <ServiceProjectDetail /> },
|
||||
{ path: "/service-projects/:projectId", element: <ServiceProjectDetail /> },
|
||||
{path:"/service/job",element:<ManageJob/>},
|
||||
|
||||
{ path: "/employees", element: <EmployeeList /> },
|
||||
|
||||
@ -35,9 +35,9 @@ const localVariablesSlice = createSlice({
|
||||
selfTenant: {
|
||||
tenantEnquireId: null,
|
||||
planId: null,
|
||||
details:null,
|
||||
frequency:null,
|
||||
paymentDetailId:null
|
||||
details: null,
|
||||
frequency: null,
|
||||
paymentDetailId: null,
|
||||
},
|
||||
},
|
||||
reducers: {
|
||||
@ -94,25 +94,38 @@ const localVariablesSlice = createSlice({
|
||||
|
||||
openModal: (state, action) => {
|
||||
const { modalType, data } = action.payload;
|
||||
state.modals[modalType] = { isOpen: true, ...data };
|
||||
|
||||
state.modals[modalType] = {
|
||||
isOpen: true,
|
||||
data: data ?? {},
|
||||
};
|
||||
},
|
||||
|
||||
closeModal: (state, action) => {
|
||||
const { modalType } = action.payload;
|
||||
state.modals[modalType] = { ...state.modals[modalType], isOpen: false };
|
||||
state.modals[modalType] = {
|
||||
isOpen: false,
|
||||
data: null,
|
||||
};
|
||||
},
|
||||
|
||||
toggleModal: (state, action) => {
|
||||
const { modalType } = action.payload;
|
||||
state.modals[modalType].isOpen = !state.modals[modalType].isOpen;
|
||||
const modal = state.modals[modalType] || {};
|
||||
modal.isOpen = !modal.isOpen;
|
||||
},
|
||||
|
||||
setSelfTenant: (state, action) => {
|
||||
state.selfTenant.tenantEnquireId =
|
||||
action.payload.tenantEnquireId ?? state.selfTenant.tenantEnquireId;
|
||||
state.selfTenant.planId =
|
||||
action.payload.planId ?? state.selfTenant.planId;
|
||||
state.selfTenant.details =
|
||||
state.selfTenant.details =
|
||||
action.payload.details ?? state.selfTenant.details;
|
||||
state.selfTenant.frequency = action.payload.frequency ?? state.selfTenant.frequency;
|
||||
state.selfTenant.paymentDetailId = action.payload.paymentDetailId ?? state.selfTenant.paymentDetailId;
|
||||
state.selfTenant.frequency =
|
||||
action.payload.frequency ?? state.selfTenant.frequency;
|
||||
state.selfTenant.paymentDetailId =
|
||||
action.payload.paymentDetailId ?? state.selfTenant.paymentDetailId;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user