Merge pull request 'Team Assign ToProject : With other Organization employees' (#437) from TeamAssignToProject into Organization_Management
Reviewed-on: #437 Merged
This commit is contained in:
commit
d52fa00de0
4
public/assets/vendor/css/core.css
vendored
4
public/assets/vendor/css/core.css
vendored
@ -18613,6 +18613,10 @@ li:not(:first-child) .dropdown-item,
|
|||||||
min-height: 70vh !important;
|
min-height: 70vh !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-min-h{
|
||||||
|
min-height: 60vh !important;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-fill {
|
.flex-fill {
|
||||||
flex: 1 1 auto !important;
|
flex: 1 1 auto !important;
|
||||||
}
|
}
|
||||||
|
80
src/components/Project/Team/TeamAssignToProject.jsx
Normal file
80
src/components/Project/Team/TeamAssignToProject.jsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import TeamEmployeeList from "./TeamEmployeeList";
|
||||||
|
import { useOrganization } from "../../../hooks/useDirectory";
|
||||||
|
import { useOrganizationsList } from "../../../hooks/useOrganization";
|
||||||
|
import { useProjectAssignedOrganizations } from "../../../hooks/useProjects";
|
||||||
|
import { useSelectedProject } from "../../../slices/apiDataManager";
|
||||||
|
|
||||||
|
const TeamAssignToProject = ({ closeModal }) => {
|
||||||
|
const [searchText, setSearchText] = useState("");
|
||||||
|
const [selectedOrg, setSelectedOrg] = useState(null);
|
||||||
|
const project = useSelectedProject();
|
||||||
|
const { data, isLoading, isError, error } =
|
||||||
|
useProjectAssignedOrganizations(project);
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<p className="fs-5 fs-seminbod ">Assign Employee To Project </p>
|
||||||
|
|
||||||
|
<div class="row align-items-center gx-5">
|
||||||
|
<div class="col">
|
||||||
|
<div className="d-flex flex-grow-1 align-items-center gap-2">
|
||||||
|
{isLoading ? (
|
||||||
|
<select className="form-select form-select-sm w-100" disabled>
|
||||||
|
<option value="">Loading...</option>
|
||||||
|
</select>
|
||||||
|
) : data?.length === 0 ? (
|
||||||
|
<p className="mb-0 badge bg-label-secondary">No organizations found</p>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<label
|
||||||
|
htmlFor="organization"
|
||||||
|
className="form-label mb-0 text-nowrap"
|
||||||
|
>
|
||||||
|
Select Organization
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="organization"
|
||||||
|
className="form-select form-select-sm w-100"
|
||||||
|
value={selectedOrg || ""}
|
||||||
|
onChange={(e) => setSelectedOrg(e.target.value)}
|
||||||
|
>
|
||||||
|
<option value="">Select</option>
|
||||||
|
{data.map((org) => (
|
||||||
|
<option key={org.id} value={org.id}>
|
||||||
|
{org.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div className="d-flex flex-grow-1 align-items-center gap-2">
|
||||||
|
<label htmlFor="search" className="form-label mb-0 text-nowrap">
|
||||||
|
Search Employee
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="search"
|
||||||
|
type="search"
|
||||||
|
className="form-control form-control-sm w-100"
|
||||||
|
placeholder="Search..."
|
||||||
|
value={searchText}
|
||||||
|
onChange={(e) => setSearchText(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div >
|
||||||
|
<TeamEmployeeList
|
||||||
|
organizationId={selectedOrg}
|
||||||
|
searchTerm={searchText}
|
||||||
|
closeModal={closeModal}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TeamAssignToProject;
|
250
src/components/Project/Team/TeamEmployeeList.jsx
Normal file
250
src/components/Project/Team/TeamEmployeeList.jsx
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import Avatar from "../../common/Avatar";
|
||||||
|
import { useDebounce } from "../../../utils/appUtils";
|
||||||
|
import { useSelectedProject } from "../../../slices/apiDataManager";
|
||||||
|
import { useOrganizationEmployees } from "../../../hooks/useOrganization";
|
||||||
|
import {
|
||||||
|
useEmployeesByProjectAllocated,
|
||||||
|
useManageProjectAllocation,
|
||||||
|
} from "../../../hooks/useProjects";
|
||||||
|
import useMaster, { useServices } from "../../../hooks/masterHook/useMaster";
|
||||||
|
import showToast from "../../../services/toastService";
|
||||||
|
|
||||||
|
const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
|
||||||
|
const selectedProject = useSelectedProject();
|
||||||
|
const debounceSearchTerm = useDebounce(searchTerm, 500);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: employeesData = [],
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
} = useOrganizationEmployees(
|
||||||
|
selectedProject,
|
||||||
|
organizationId,
|
||||||
|
debounceSearchTerm
|
||||||
|
);
|
||||||
|
|
||||||
|
const { projectEmployees, loading: employeeLodaing } =
|
||||||
|
useEmployeesByProjectAllocated(selectedProject, null);
|
||||||
|
|
||||||
|
const { data: jobRoles } = useMaster();
|
||||||
|
const { data: services } = useServices();
|
||||||
|
|
||||||
|
const [employees, setEmployees] = useState([]);
|
||||||
|
|
||||||
|
const { mutate: handleAssignEmployee, isPending } =
|
||||||
|
useManageProjectAllocation({
|
||||||
|
onSuccessCallback: () => {
|
||||||
|
closeModal();
|
||||||
|
},
|
||||||
|
onErrorCallback: () => {
|
||||||
|
closeModal();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (employeesData?.data?.length > 0) {
|
||||||
|
const available = employeesData.data.filter((emp) => {
|
||||||
|
const projEmp = projectEmployees.find((pe) => pe.employeeId === emp.id);
|
||||||
|
return !projEmp || projEmp.isActive === false;
|
||||||
|
});
|
||||||
|
|
||||||
|
setEmployees(
|
||||||
|
available.map((emp) => ({
|
||||||
|
...emp,
|
||||||
|
isChecked: false,
|
||||||
|
jobRole: emp?.jobRoleId || null,
|
||||||
|
serviceId: "",
|
||||||
|
errors: {},
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [employeesData, projectEmployees, organizationId]);
|
||||||
|
|
||||||
|
const handleCheckboxChange = (index) => {
|
||||||
|
setEmployees((prev) => {
|
||||||
|
const newArr = [...prev];
|
||||||
|
newArr[index].isChecked = !newArr[index].isChecked;
|
||||||
|
newArr[index].errors = {};
|
||||||
|
return newArr;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectChange = (index, field, value) => {
|
||||||
|
setEmployees((prev) => {
|
||||||
|
const newArr = [...prev];
|
||||||
|
newArr[index][field] = value;
|
||||||
|
newArr[index].errors[field] = "";
|
||||||
|
return newArr;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
const checkedEmployees = employees.filter((emp) => emp.isChecked);
|
||||||
|
|
||||||
|
setEmployees((prev) => prev.map((emp) => ({ ...emp, errors: {} })));
|
||||||
|
|
||||||
|
if (checkedEmployees.length === 0) {
|
||||||
|
showToast("Select at least one employee", "info");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasError = false;
|
||||||
|
const newEmployees = employees.map((emp) => {
|
||||||
|
const empErrors = {};
|
||||||
|
if (emp.isChecked) {
|
||||||
|
if (!emp.jobRole) {
|
||||||
|
empErrors.jobRole = "Job role is required";
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
if (!emp.serviceId) {
|
||||||
|
empErrors.serviceId = "Service is required";
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { ...emp, errors: empErrors };
|
||||||
|
});
|
||||||
|
|
||||||
|
setEmployees(newEmployees);
|
||||||
|
|
||||||
|
if (hasError) return; // stop submit if validation fails
|
||||||
|
|
||||||
|
const payload = checkedEmployees.map((emp) => ({
|
||||||
|
employeeId: emp.id,
|
||||||
|
jobRoleId: emp.jobRole,
|
||||||
|
serviceId: emp.serviceId,
|
||||||
|
projectId: selectedProject,
|
||||||
|
status: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
handleAssignEmployee({ payload,actionType:"assign"} );
|
||||||
|
|
||||||
|
setEmployees((prev) =>
|
||||||
|
prev.map((emp) => ({
|
||||||
|
...emp,
|
||||||
|
isChecked: false,
|
||||||
|
jobRole: "",
|
||||||
|
serviceId: "",
|
||||||
|
errors: {},
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return ( <div className="page-min-h d-flex justify-content-center align-items-center "><p className="text-muted">Loading employees...</p></div>) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return (
|
||||||
|
<div className="page-min-h d-flex justify-content-center align-items-center ">
|
||||||
|
{error?.status === 400 ? (
|
||||||
|
<p className="m-0">Enter employee you want to find.</p>
|
||||||
|
) : (
|
||||||
|
<p className="m-0 dange-text">Something went wrong. Please try again later.</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (employees.length === 0) {
|
||||||
|
return(<div className="page-min-h d-flex justify-content-center align-items-center "><p className="text-muted">No available employees to assign.</p></div>) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className=" position-relative">
|
||||||
|
<table className="table" style={{ maxHeight: "80px", overflowY: "auto" }}>
|
||||||
|
<thead className=" position-sticky top-0">
|
||||||
|
<tr>
|
||||||
|
<th>Employee</th>
|
||||||
|
<th>Service</th>
|
||||||
|
<th>Job Role</th>
|
||||||
|
<th>Select</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{employees.map((emp, index) => (
|
||||||
|
<tr key={emp.id}>
|
||||||
|
<td>
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<Avatar firstName={emp.firstName} lastName={emp.lastName} />
|
||||||
|
<span className="ms-2 fw-semibold">
|
||||||
|
{emp.firstName} {emp.lastName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select
|
||||||
|
value={emp.serviceId}
|
||||||
|
disabled={!emp.isChecked}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleSelectChange(index, "serviceId", e.target.value)
|
||||||
|
}
|
||||||
|
className={`form-select form-select-sm w-auto border-none rounded-0 py-1 px-auto ${
|
||||||
|
emp.errors.serviceId ? "is-invalid" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<option value="">Select Service</option>
|
||||||
|
{services?.data?.map((s) => (
|
||||||
|
<option key={s.id} value={s.id}>
|
||||||
|
{s.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{emp.errors.serviceId && (
|
||||||
|
<div className="danger-text">{emp.errors.serviceId}</div>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select
|
||||||
|
value={emp.jobRole}
|
||||||
|
disabled={!emp.isChecked}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleSelectChange(index, "jobRole", e.target.value)
|
||||||
|
}
|
||||||
|
className={`form-select form-select-sm w-auto border-none rounded-0 py-1 px-auto ${
|
||||||
|
emp.errors.jobRole ? "is-invalid" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<option value="">Select Job Role</option>
|
||||||
|
{jobRoles?.map((r) => (
|
||||||
|
<option key={r.id} value={r.id}>
|
||||||
|
{r.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{emp.errors.jobRole && (
|
||||||
|
<div className="danger-text">{emp.errors.jobRole}</div>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="form-check-input"
|
||||||
|
checked={emp.isChecked}
|
||||||
|
onChange={() => handleCheckboxChange(index)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div className="position-sticky bottom-0 bg-white d-flex justify-content-end gap-3 z-25 ">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-sm btn-label-secondary"
|
||||||
|
onClick={() => closeModal()}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button onClick={onSubmit} className="btn btn-primary">
|
||||||
|
{isPending ? "Please Wait..." : "Assign to Project"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TeamEmployeeList;
|
334
src/components/Project/Team/Teams.jsx
Normal file
334
src/components/Project/Team/Teams.jsx
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
||||||
|
import MapUsers from "../MapUsers";
|
||||||
|
import { Link, NavLink, useNavigate, useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
import showToast from "../../../services/toastService";
|
||||||
|
import Avatar from "../../common/Avatar";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
|
import ProjectRepository from "../../../repositories/ProjectRepository";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { changeMaster } from "../../../slices/localVariablesSlice";
|
||||||
|
import useMaster from "../../../hooks/masterHook/useMaster";
|
||||||
|
import { useHasUserPermission } from "../../../hooks/useHasUserPermission";
|
||||||
|
import { ASSIGN_TO_PROJECT } from "../../../utils/constants";
|
||||||
|
import ConfirmModal from "../../common/ConfirmModal";
|
||||||
|
import eventBus from "../../../services/eventBus";
|
||||||
|
import {
|
||||||
|
useEmployeesByProjectAllocated,
|
||||||
|
useManageProjectAllocation,
|
||||||
|
useProjectAssignedServices,
|
||||||
|
} from "../../../hooks/useProjects";
|
||||||
|
import { useSelectedProject } from "../../../slices/apiDataManager";
|
||||||
|
import GlobalModel from "../../common/GlobalModel";
|
||||||
|
import TeamAssignToProject from "./TeamAssignToProject";
|
||||||
|
|
||||||
|
const Teams = () => {
|
||||||
|
const selectedProject = useSelectedProject();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [AssigTeam, setAssignTeam] = useState(false);
|
||||||
|
const [employees, setEmployees] = useState([]);
|
||||||
|
const [selectedEmployee, setSelectedEmployee] = useState(null);
|
||||||
|
const [deleteEmployee, setDeleteEmplyee] = useState(null);
|
||||||
|
const [searchTerm, setSearchTerm] = useState(""); // State for search term
|
||||||
|
const [selectedService, setSelectedService] = useState(null);
|
||||||
|
const [activeEmployee, setActiveEmployee] = useState(false);
|
||||||
|
|
||||||
|
const { data: assignedServices, isLoading: servicesLoading } =
|
||||||
|
useProjectAssignedServices(selectedProject);
|
||||||
|
const { data: empJobRoles, loading } = useMaster();
|
||||||
|
const handleToggleActive = (e) => setActiveEmployee(e.target.checked);
|
||||||
|
|
||||||
|
const handleServiceChange = (e) => {
|
||||||
|
setSelectedService(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const HasAssignUserPermission = useHasUserPermission(ASSIGN_TO_PROJECT);
|
||||||
|
const [IsDeleteModal, setIsDeleteModal] = useState(false);
|
||||||
|
const {
|
||||||
|
projectEmployees,
|
||||||
|
loading: employeeLodaing,
|
||||||
|
refetch,
|
||||||
|
} = useEmployeesByProjectAllocated(
|
||||||
|
selectedProject,
|
||||||
|
selectedService,
|
||||||
|
null,
|
||||||
|
activeEmployee
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
mutate: submitAllocations,
|
||||||
|
isPending,
|
||||||
|
isSuccess,
|
||||||
|
isError,
|
||||||
|
} = useManageProjectAllocation({
|
||||||
|
onSuccessCallback: () => {
|
||||||
|
setSelectedEmployee(null);
|
||||||
|
},
|
||||||
|
onErrorCallback: () => {
|
||||||
|
setSelectedEmployee(null);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDelete = (employee) => {
|
||||||
|
let payload = [
|
||||||
|
{
|
||||||
|
employeeId: employee?.employeeId,
|
||||||
|
jobRoleId: employee?.jobRoleId,
|
||||||
|
projectId: selectedProject,
|
||||||
|
serviceId: employee?.serviceId,
|
||||||
|
status: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
submitAllocations({ payload: payload, actionType: "remove" });
|
||||||
|
};
|
||||||
|
const getJobRole = (jobRoleId) => {
|
||||||
|
if (loading) return "Loading...";
|
||||||
|
if (!Array.isArray(empJobRoles)) return "Unassigned";
|
||||||
|
if (!jobRoleId) return "Unassigned";
|
||||||
|
|
||||||
|
const role = empJobRoles.find((b) => b.id == jobRoleId);
|
||||||
|
return role ? role.name : "Unassigned";
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredEmployees = useMemo(() => {
|
||||||
|
if (!projectEmployees || !searchTerm?.trim()) return projectEmployees;
|
||||||
|
|
||||||
|
const lower = searchTerm.toLowerCase();
|
||||||
|
|
||||||
|
return projectEmployees?.filter((emp) => {
|
||||||
|
const fullName = `${emp.firstName ?? ""} ${
|
||||||
|
emp.lastName ?? ""
|
||||||
|
}`.toLowerCase();
|
||||||
|
|
||||||
|
const joberole = getJobRole(emp?.jobRoleId)?.toLowerCase();
|
||||||
|
|
||||||
|
return fullName?.includes(lower) || joberole?.includes(lower);
|
||||||
|
});
|
||||||
|
}, [projectEmployees, searchTerm]);
|
||||||
|
const handleSearch = (e) => setSearchTerm(e.target.value);
|
||||||
|
const employeeHandler = useCallback(
|
||||||
|
(msg) => {
|
||||||
|
if (filteredEmployees.some((emp) => emp.employeeId == msg.employeeId)) {
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[filteredEmployees, refetch]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
eventBus.on("employee", employeeHandler);
|
||||||
|
return () => eventBus.off("employee", employeeHandler);
|
||||||
|
}, [employeeHandler]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{AssigTeam && (
|
||||||
|
<GlobalModel
|
||||||
|
size="lg"
|
||||||
|
isOpen={AssigTeam}
|
||||||
|
closeModal={() => setAssignTeam(false)}
|
||||||
|
>
|
||||||
|
<TeamAssignToProject closeModal={() => setAssignTeam(false)} />
|
||||||
|
</GlobalModel>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
type="delete"
|
||||||
|
header={"Remove Employee"}
|
||||||
|
message={"Are you sure you want to remove?"}
|
||||||
|
isOpen={!!selectedEmployee}
|
||||||
|
loading={isPending}
|
||||||
|
onSubmit={() => handleDelete(selectedEmployee)}
|
||||||
|
onClose={() => setSelectedEmployee(null)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="card card-action mb-6">
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="row align-items-center justify-content-between mb-4 g-3">
|
||||||
|
<div className="col-md-6 col-12 algin-items-center">
|
||||||
|
<div className="d-flex flex-wrap align-items-center gap-3">
|
||||||
|
<div>
|
||||||
|
{!servicesLoading && (
|
||||||
|
<>
|
||||||
|
{(!assignedServices || assignedServices.length === 0) && (
|
||||||
|
<span className="badge bg-label-secondary">
|
||||||
|
Not Service Assigned
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{assignedServices?.length === 1 && (
|
||||||
|
<span className="badge bg-label-secondary">
|
||||||
|
{assignedServices[0].name}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{assignedServices?.length > 1 && (
|
||||||
|
<select
|
||||||
|
className="form-select form-select-sm"
|
||||||
|
aria-label="Select Service"
|
||||||
|
value={selectedService}
|
||||||
|
onChange={handleServiceChange}
|
||||||
|
>
|
||||||
|
<option value="">All Services</option>
|
||||||
|
{assignedServices.map((service) => (
|
||||||
|
<option key={service.id} value={service.id}>
|
||||||
|
{service.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-check form-switch d-flex align-items-center text-nowrap">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="form-check-input"
|
||||||
|
id="activeEmployeeSwitch"
|
||||||
|
checked={activeEmployee}
|
||||||
|
onChange={handleToggleActive}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
className="form-check-label ms-2"
|
||||||
|
htmlFor="activeEmployeeSwitch"
|
||||||
|
>
|
||||||
|
{activeEmployee ? "Active Employees" : "Inactive Employees"}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-6 col-12 d-flex justify-content-md-end align-items-center justify-content-start gap-3">
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
className="form-control form-control-sm"
|
||||||
|
placeholder="Search by Name or Role"
|
||||||
|
aria-controls="DataTables_Table_0"
|
||||||
|
style={{ maxWidth: "200px" }}
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={handleSearch}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{HasAssignUserPermission && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-primary btn-sm text-nowrap"
|
||||||
|
onClick={() => setAssignTeam(true)}
|
||||||
|
>
|
||||||
|
<i className="bx bx-plus-circle me-1"></i>
|
||||||
|
Assign Employee
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="table-responsive text-nowrap modal-min-h">
|
||||||
|
{employeeLodaing && <p>Loading..</p>}
|
||||||
|
{projectEmployees && projectEmployees.length > 0 && (
|
||||||
|
<table className="table ">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<div className="text-start ms-5">Name</div>
|
||||||
|
</th>
|
||||||
|
<th>Services</th>
|
||||||
|
<th>Organization</th>
|
||||||
|
<th>Assigned Date</th>
|
||||||
|
{activeEmployee && <th>Release Date</th>}
|
||||||
|
<th>Project Role</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="table-border-bottom-0">
|
||||||
|
{filteredEmployees &&
|
||||||
|
filteredEmployees
|
||||||
|
.sort((a, b) =>
|
||||||
|
(a.firstName || "").localeCompare(b.firstName || "")
|
||||||
|
)
|
||||||
|
.map((emp) => (
|
||||||
|
<tr key={emp.id}>
|
||||||
|
<td>
|
||||||
|
<div className="d-flex justify-content-start align-items-center">
|
||||||
|
<Avatar
|
||||||
|
firstName={emp.firstName}
|
||||||
|
lastName={emp.lastName}
|
||||||
|
/>
|
||||||
|
<div className="d-flex flex-column">
|
||||||
|
<a
|
||||||
|
onClick={() =>
|
||||||
|
navigate(
|
||||||
|
`/employee/${emp.employeeId}?for=attendance`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className="text-heading text-truncate cursor-pointer"
|
||||||
|
>
|
||||||
|
<span className="fw-normal">
|
||||||
|
{emp.firstName}
|
||||||
|
{emp.lastName}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>{emp.serviceName || "N/A"}</td>
|
||||||
|
<td>{emp.organizationName || "N/A"}</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
{moment(emp.allocationDate).format("DD-MMM-YYYY")}
|
||||||
|
</td>
|
||||||
|
{activeEmployee && (
|
||||||
|
<td>
|
||||||
|
{emp.reAllocationDate
|
||||||
|
? moment(emp.reAllocationDate).format(
|
||||||
|
"DD-MMM-YYYY"
|
||||||
|
)
|
||||||
|
: "Present"}
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
|
<td>
|
||||||
|
<span className="badge bg-label-primary me-1">
|
||||||
|
{getJobRole(emp.jobRoleId)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{emp.isActive ? (
|
||||||
|
<button
|
||||||
|
aria-label="Delete"
|
||||||
|
type="button"
|
||||||
|
title="Remove from project"
|
||||||
|
className="btn p-0 dropdown-toggle hide-arrow"
|
||||||
|
onClick={() => setSelectedEmployee(emp)}
|
||||||
|
>
|
||||||
|
<i className="bx bx-trash me-1 text-danger"></i>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<span>Not in project</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
|
{!employeeLodaing && filteredEmployees.length === 0 && (
|
||||||
|
<div className="text-center text-muted py-3 d-flex justify-content-center align-items-center py-12">
|
||||||
|
<p>
|
||||||
|
{!activeEmployee
|
||||||
|
? "No active employees assigned to the project"
|
||||||
|
: "No inactive employees assigned to the project"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Teams;
|
@ -1,443 +0,0 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
|
||||||
import MapUsers from "./MapUsers";
|
|
||||||
import { Link, NavLink, useNavigate, useParams } from "react-router-dom";
|
|
||||||
|
|
||||||
import showToast from "../../services/toastService";
|
|
||||||
import Avatar from "../common/Avatar";
|
|
||||||
import moment from "moment";
|
|
||||||
|
|
||||||
import ProjectRepository from "../../repositories/ProjectRepository";
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
import { changeMaster } from "../../slices/localVariablesSlice";
|
|
||||||
import useMaster from "../../hooks/masterHook/useMaster";
|
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
|
||||||
import { ASSIGN_TO_PROJECT } from "../../utils/constants";
|
|
||||||
import ConfirmModal from "../common/ConfirmModal";
|
|
||||||
import eventBus from "../../services/eventBus";
|
|
||||||
import {
|
|
||||||
useEmployeesByProjectAllocated,
|
|
||||||
useManageProjectAllocation,
|
|
||||||
useProjectAssignedServices,
|
|
||||||
} from "../../hooks/useProjects";
|
|
||||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
|
||||||
|
|
||||||
const Teams = () => {
|
|
||||||
const projectId = useSelectedProject();
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
|
|
||||||
const { data, loading } = useMaster();
|
|
||||||
const [isModalOpen, setIsModelOpen] = useState(false);
|
|
||||||
const [error, setError] = useState("");
|
|
||||||
const [empJobRoles, setEmpJobRoles] = useState(null);
|
|
||||||
const [employees, setEmployees] = useState([]);
|
|
||||||
const [filteredEmployees, setFilteredEmployees] = useState([]);
|
|
||||||
const [removingEmployeeId, setRemovingEmployeeId] = useState(null);
|
|
||||||
const [assignedLoading, setAssignedLoading] = useState(false);
|
|
||||||
const [activeEmployee, setActiveEmployee] = useState(true);
|
|
||||||
const [deleteEmployee, setDeleteEmplyee] = useState(null);
|
|
||||||
const [searchTerm, setSearchTerm] = useState(""); // State for search term
|
|
||||||
const [selectedService, setSelectedService] = useState(null);
|
|
||||||
|
|
||||||
const { data: assignedServices, isLoading: servicesLoading } = useProjectAssignedServices(projectId);
|
|
||||||
const handleToggleActive = e => setActiveEmployee(e.target.checked);
|
|
||||||
|
|
||||||
|
|
||||||
const handleServiceChange = (e) => {
|
|
||||||
setSelectedService(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const HasAssignUserPermission = useHasUserPermission(ASSIGN_TO_PROJECT);
|
|
||||||
const [IsDeleteModal, setIsDeleteModal] = useState(false);
|
|
||||||
const {
|
|
||||||
projectEmployees,
|
|
||||||
loading: employeeLodaing,
|
|
||||||
refetch,
|
|
||||||
} = useEmployeesByProjectAllocated(projectId, selectedService);
|
|
||||||
const {
|
|
||||||
mutate: submitAllocations,
|
|
||||||
isPending,
|
|
||||||
isSuccess,
|
|
||||||
isError,
|
|
||||||
} = useManageProjectAllocation({
|
|
||||||
onSuccessCallback: () => {
|
|
||||||
setRemovingEmployeeId(null);
|
|
||||||
setAssignedLoading(false);
|
|
||||||
setDeleteEmplyee(null);
|
|
||||||
closeDeleteModal();
|
|
||||||
},
|
|
||||||
onErrorCallback: () => {
|
|
||||||
closeDeleteModal();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const removeAllocation = (item) => {
|
|
||||||
setRemovingEmployeeId(item.id);
|
|
||||||
|
|
||||||
submitAllocations({
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
empID: item.employeeId,
|
|
||||||
jobRoleId: item.jobRoleId,
|
|
||||||
projectId: projectId,
|
|
||||||
status: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
added: false,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEmpAlicationFormSubmit = (allocaionObj) => {
|
|
||||||
let items = allocaionObj.map((item) => {
|
|
||||||
return {
|
|
||||||
empID: item.empID,
|
|
||||||
jobRoleId: item.jobRoleId,
|
|
||||||
projectId: projectId,
|
|
||||||
status: true,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
submitAllocations({ items, added: true });
|
|
||||||
|
|
||||||
setActiveEmployee(true);
|
|
||||||
setFilteredEmployees(employees.filter((emp) => emp.isActive));
|
|
||||||
|
|
||||||
const dropdown = document.querySelector(
|
|
||||||
'select[name="DataTables_Table_0_length"]'
|
|
||||||
);
|
|
||||||
if (dropdown) dropdown.value = "true";
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRole = (jobRoleId) => {
|
|
||||||
if (loading) return "Loading...";
|
|
||||||
if (!Array.isArray(empJobRoles)) return "Unassigned";
|
|
||||||
if (!jobRoleId) return "Unassigned";
|
|
||||||
|
|
||||||
const role = empJobRoles.find((b) => b.id == jobRoleId);
|
|
||||||
return role ? role.name : "Unassigned";
|
|
||||||
};
|
|
||||||
const openModel = () => {
|
|
||||||
setIsModelOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onModelClose = () => {
|
|
||||||
setIsModelOpen(false);
|
|
||||||
const modalElement = document.getElementById("user-model");
|
|
||||||
if (modalElement) {
|
|
||||||
modalElement.classList.remove("show");
|
|
||||||
modalElement.style.display = "none";
|
|
||||||
document.body.classList.remove("modal-open");
|
|
||||||
document.querySelector(".modal-backdrop").remove();
|
|
||||||
}
|
|
||||||
const modalBackdropElement = document.querySelector(".modal-backdrop");
|
|
||||||
if (modalBackdropElement) {
|
|
||||||
modalBackdropElement.remove();
|
|
||||||
}
|
|
||||||
document.body.style.overflow = "auto";
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(changeMaster("Job Role"));
|
|
||||||
}, [dispatch]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (projectEmployees) {
|
|
||||||
setEmployees(projectEmployees);
|
|
||||||
const filtered = projectEmployees.filter((emp) => emp.isActive);
|
|
||||||
setFilteredEmployees(filtered);
|
|
||||||
}
|
|
||||||
}, [projectEmployees, employeeLodaing]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data) {
|
|
||||||
setEmpJobRoles(data);
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
const filterAndSearchEmployees = useCallback(() => {
|
|
||||||
const statusFiltered = employees.filter((emp) =>
|
|
||||||
activeEmployee ? emp.isActive : !emp.isActive
|
|
||||||
);
|
|
||||||
|
|
||||||
if (searchTerm === "") {
|
|
||||||
setFilteredEmployees(statusFiltered);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lowercasedSearchTerm = searchTerm.toLowerCase();
|
|
||||||
|
|
||||||
const searchedAndFiltered = statusFiltered.filter((item) => {
|
|
||||||
const fullName = `${item.firstName} ${item.middleName} ${item.lastName}`.toLowerCase();
|
|
||||||
const roleName = getRole(item.jobRoleId).toLowerCase();
|
|
||||||
const orgName = (item.organizationName || "").toLowerCase();
|
|
||||||
const serviceName = (item.serviceName || "").toLowerCase();
|
|
||||||
|
|
||||||
return (
|
|
||||||
fullName.includes(lowercasedSearchTerm) ||
|
|
||||||
roleName.includes(lowercasedSearchTerm) ||
|
|
||||||
orgName.includes(lowercasedSearchTerm) ||
|
|
||||||
serviceName.includes(lowercasedSearchTerm)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
setFilteredEmployees(searchedAndFiltered);
|
|
||||||
}, [employees, activeEmployee, searchTerm, getRole]);
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
filterAndSearchEmployees();
|
|
||||||
}, [employees, activeEmployee, searchTerm, filterAndSearchEmployees]);
|
|
||||||
|
|
||||||
const handleFilterEmployee = (e) => {
|
|
||||||
const filterValue = e.target.value;
|
|
||||||
setActiveEmployee(filterValue === "true");
|
|
||||||
setSearchTerm("");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSearch = (e) => {
|
|
||||||
setSearchTerm(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteModalOpen = (item) => {
|
|
||||||
setDeleteEmplyee(item);
|
|
||||||
setIsDeleteModal(true);
|
|
||||||
};
|
|
||||||
const closeDeleteModal = () => setIsDeleteModal(false);
|
|
||||||
|
|
||||||
const handler = useCallback(
|
|
||||||
(msg) => {
|
|
||||||
if (msg.projectIds.some((item) => item === projectId)) {
|
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[projectId, refetch]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
eventBus.on("assign_project_all", handler);
|
|
||||||
return () => eventBus.off("assign_project_all", handler);
|
|
||||||
}, [handler]);
|
|
||||||
|
|
||||||
const employeeHandler = useCallback(
|
|
||||||
(msg) => {
|
|
||||||
if (filteredEmployees.some((item) => item.employeeId == msg.employeeId)) {
|
|
||||||
refetch();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[filteredEmployees, refetch]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
eventBus.on("employee", employeeHandler);
|
|
||||||
return () => eventBus.off("employee", employeeHandler);
|
|
||||||
}, [employeeHandler]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className="modal fade"
|
|
||||||
id="user-model"
|
|
||||||
tabIndex="-1"
|
|
||||||
aria-labelledby="userModalLabel"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<MapUsers
|
|
||||||
projectId={projectId}
|
|
||||||
onClose={onModelClose}
|
|
||||||
empJobRoles={empJobRoles}
|
|
||||||
onSubmit={handleEmpAlicationFormSubmit}
|
|
||||||
allocation={employees}
|
|
||||||
assignedLoading={assignedLoading}
|
|
||||||
setAssignedLoading={setAssignedLoading}
|
|
||||||
></MapUsers>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{IsDeleteModal && (
|
|
||||||
<ConfirmModal
|
|
||||||
isOpen={IsDeleteModal}
|
|
||||||
type="delete"
|
|
||||||
header="Remove Employee"
|
|
||||||
message="Are you sure you want to delete?"
|
|
||||||
onSubmit={() => removeAllocation(deleteEmployee)}
|
|
||||||
onClose={closeDeleteModal}
|
|
||||||
loading={isPending}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="card card-action mb-6">
|
|
||||||
<div className="card-body">
|
|
||||||
<div className="row d-flex justify-content-between mb-4">
|
|
||||||
<div className="col-md-6 col-12 d-flex align-items-center">
|
|
||||||
<div className="dataTables_length text-start py-1 px-0 col-md-4 col-12">
|
|
||||||
{!servicesLoading && assignedServices?.length > 0 && (
|
|
||||||
assignedServices.length > 1 ? (
|
|
||||||
<label>
|
|
||||||
<select
|
|
||||||
name="DataTables_Table_0_length"
|
|
||||||
aria-controls="DataTables_Table_0"
|
|
||||||
className="form-select form-select-sm"
|
|
||||||
aria-label="Select Service"
|
|
||||||
value={selectedService}
|
|
||||||
onChange={handleServiceChange}
|
|
||||||
style={{ fontSize: "0.875rem", height: "35px", width: "190px" }}
|
|
||||||
>
|
|
||||||
<option value="">All Services</option>
|
|
||||||
{assignedServices.map((service) => (
|
|
||||||
<option key={service.id} value={service.id}>
|
|
||||||
{service.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
) : (
|
|
||||||
<h5>{assignedServices[0].name}</h5>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="col-md-6 col-12 d-flex justify-content-end align-items-center">
|
|
||||||
<div className="form-check form-switch me-2 mt-2">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="form-check-input"
|
|
||||||
checked={activeEmployee}
|
|
||||||
onChange={handleToggleActive}
|
|
||||||
id="activeEmployeeSwitch"
|
|
||||||
/>
|
|
||||||
<label className="form-check-label ms-0 " htmlFor="activeEmployeeSwitch">
|
|
||||||
{activeEmployee ? "Active Employees" : "Inactive Employees"}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="dataTables_filter d-inline-flex align-items-center ms-2">x``
|
|
||||||
<input
|
|
||||||
type="search"
|
|
||||||
className="form-control form-control-sm me-4"
|
|
||||||
placeholder="Search by Name or Role"
|
|
||||||
aria-controls="DataTables_Table_0"
|
|
||||||
value={searchTerm}
|
|
||||||
onChange={handleSearch}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`link-button btn-primary btn-sm ${HasAssignUserPermission ? "" : "d-none"
|
|
||||||
}`}
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
data-bs-target="#user-model"
|
|
||||||
>
|
|
||||||
<i className="bx bx-plus-circle me-2"></i>
|
|
||||||
Assign Employee
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="table-responsive text-nowrap">
|
|
||||||
{employeeLodaing && <p>Loading..</p>}
|
|
||||||
{!employeeLodaing &&
|
|
||||||
filteredEmployees &&
|
|
||||||
filteredEmployees.length > 0 && (
|
|
||||||
<table className="table ">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<div className="text-start ms-5">Name</div>
|
|
||||||
</th>
|
|
||||||
<th>Services</th>
|
|
||||||
<th>Organization</th>
|
|
||||||
<th>Assigned Date</th>
|
|
||||||
{!activeEmployee && <th>Release Date</th>}
|
|
||||||
<th>Project Role</th>
|
|
||||||
<th>Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="table-border-bottom-0">
|
|
||||||
{filteredEmployees &&
|
|
||||||
filteredEmployees.map((item) => (
|
|
||||||
<tr key={item.id}>
|
|
||||||
<td>
|
|
||||||
<div className="d-flex justify-content-start align-items-center">
|
|
||||||
<Avatar
|
|
||||||
firstName={item.firstName}
|
|
||||||
lastName={item.lastName}
|
|
||||||
/>
|
|
||||||
<div className="d-flex flex-column">
|
|
||||||
<a
|
|
||||||
onClick={() =>
|
|
||||||
navigate(
|
|
||||||
`/employee/${item.employeeId}?for=attendance`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="text-heading text-truncate cursor-pointer"
|
|
||||||
>
|
|
||||||
<span className="fw-normal">
|
|
||||||
{item.firstName} {item.middleName}{" "}
|
|
||||||
{item.lastName}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>{item.serviceName || "N/A"}</td>
|
|
||||||
<td>{item.organizationName || "N/A"}</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
{moment(item.allocationDate).format("DD-MMM-YYYY")}
|
|
||||||
</td>
|
|
||||||
{!activeEmployee && (
|
|
||||||
<td>
|
|
||||||
{item.reAllocationDate
|
|
||||||
? moment(item.reAllocationDate).format("DD-MMM-YYYY")
|
|
||||||
: "Present"}
|
|
||||||
</td>
|
|
||||||
)}
|
|
||||||
<td>
|
|
||||||
<span className="badge bg-label-primary me-1">
|
|
||||||
{getRole(item.jobRoleId)}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{item.isActive ? (
|
|
||||||
<button
|
|
||||||
aria-label="Delete"
|
|
||||||
type="button"
|
|
||||||
title="Remove from project"
|
|
||||||
className="btn p-0 dropdown-toggle hide-arrow"
|
|
||||||
onClick={() => deleteModalOpen(item)}
|
|
||||||
>
|
|
||||||
{removingEmployeeId === item.id ? (
|
|
||||||
<div
|
|
||||||
className="spinner-border spinner-border-sm text-primary"
|
|
||||||
role="status"
|
|
||||||
>
|
|
||||||
<span className="visually-hidden">Loading...</span>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<i className="bx bx-trash me-1 text-danger"></i>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<span>Not in project</span>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
)}
|
|
||||||
{!employeeLodaing && filteredEmployees.length === 0 && (
|
|
||||||
<div className="text-center text-muted py-3">
|
|
||||||
{activeEmployee
|
|
||||||
? "No active employees assigned to the project"
|
|
||||||
: "No inactive employees assigned to the project"}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Teams;
|
|
@ -35,6 +35,8 @@ export const useOrganizationModal = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ================================Query=============================================================
|
||||||
|
|
||||||
export const useOrganizationBySPRID = (sprid) => {
|
export const useOrganizationBySPRID = (sprid) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["organization by", sprid],
|
queryKey: ["organization by", sprid],
|
||||||
@ -76,6 +78,25 @@ export const useOrganizationsList = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useOrganizationEmployees = (
|
||||||
|
projectId,
|
||||||
|
organizationId,
|
||||||
|
searchString
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["OrgEmployees", projectId, organizationId, searchString],
|
||||||
|
queryFn: async () =>
|
||||||
|
await OrganizationRepository.getOrganizationEmployees(
|
||||||
|
projectId,
|
||||||
|
organizationId,
|
||||||
|
searchString
|
||||||
|
),
|
||||||
|
enabled: !!projectId ,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// =================================Mutation========================================================
|
||||||
|
|
||||||
export const useCreateOrganization = (onSuccessCallback) => {
|
export const useCreateOrganization = (onSuccessCallback) => {
|
||||||
const useClient = useQueryClient();
|
const useClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@ -103,11 +124,11 @@ export const useAssignOrgToProject = (onSuccessCallback) => {
|
|||||||
mutationFn: async (payload) =>
|
mutationFn: async (payload) =>
|
||||||
await OrganizationRepository.assignOrganizationToProject(payload),
|
await OrganizationRepository.assignOrganizationToProject(payload),
|
||||||
onSuccess: (_, variables) => {
|
onSuccess: (_, variables) => {
|
||||||
const {projectId} = variables
|
const { projectId } = variables;
|
||||||
useClient.invalidateQueries({
|
useClient.invalidateQueries({
|
||||||
queryKey: ["projectAssignedOrganiztions"],
|
queryKey: ["projectAssignedOrganiztions"],
|
||||||
});
|
});
|
||||||
useClient.invalidateQueries({
|
useClient.invalidateQueries({
|
||||||
queryKey: ["projectAssignedOrganization", projectId],
|
queryKey: ["projectAssignedOrganization", projectId],
|
||||||
});
|
});
|
||||||
showToast("Organization successfully", "success");
|
showToast("Organization successfully", "success");
|
||||||
|
@ -6,21 +6,24 @@ import { VIEW_PROJECTS } from "../utils/constants";
|
|||||||
import showToast from "../services/toastService";
|
import showToast from "../services/toastService";
|
||||||
|
|
||||||
export const useProjectAccess = (projectId) => {
|
export const useProjectAccess = (projectId) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { data: projectPermissions, isLoading, isFetched } =
|
const { data: projectPermissions, isLoading, isFetched } =
|
||||||
useAllProjectLevelPermissions(projectId);
|
useAllProjectLevelPermissions(projectId);
|
||||||
|
|
||||||
const canView = useHasUserPermission(VIEW_PROJECTS);
|
const canView = useHasUserPermission(VIEW_PROJECTS);
|
||||||
const navigate = useNavigate();
|
|
||||||
|
const loading = isLoading || !isFetched;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (projectId && isFetched && !isLoading && !canView) {
|
if (projectId && !loading && !canView) {
|
||||||
showToast("You don't have permission to view project details", "warning");
|
showToast("You don't have permission to view project details", "warning");
|
||||||
navigate("/projects");
|
navigate("/projects");
|
||||||
}
|
}
|
||||||
}, [projectId, isFetched, isLoading, canView, navigate]);
|
}, [projectId, loading, canView, navigate]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
canView,
|
canView,
|
||||||
loading: isLoading || !isFetched,
|
loading,
|
||||||
};
|
};
|
||||||
};
|
};
|
@ -54,7 +54,7 @@ export const useProjects = () => {
|
|||||||
export const useEmployeesByProjectAllocated = (
|
export const useEmployeesByProjectAllocated = (
|
||||||
projectId,
|
projectId,
|
||||||
serviceId,
|
serviceId,
|
||||||
organizationId,
|
organizationId,emloyeeeStatus
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
data = [],
|
data = [],
|
||||||
@ -62,12 +62,12 @@ export const useEmployeesByProjectAllocated = (
|
|||||||
refetch,
|
refetch,
|
||||||
error,
|
error,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["empListByProjectAllocated", projectId, serviceId,organizationId],
|
queryKey: ["empListByProjectAllocated", projectId, serviceId,organizationId,emloyeeeStatus],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await ProjectRepository.getProjectAllocation(
|
const res = await ProjectRepository.getProjectAllocation(
|
||||||
projectId,
|
projectId, serviceId,
|
||||||
organizationId,
|
organizationId,
|
||||||
serviceId
|
emloyeeeStatus
|
||||||
);
|
);
|
||||||
return res?.data || res;
|
return res?.data || res;
|
||||||
},
|
},
|
||||||
@ -413,8 +413,8 @@ export const useManageProjectAllocation = ({
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const { mutate, isPending, isSuccess, isError } = useMutation({
|
const { mutate, isPending, isSuccess, isError } = useMutation({
|
||||||
mutationFn: async ({ items }) => {
|
mutationFn: async ({payload}) => {
|
||||||
const response = await ProjectRepository.manageProjectAllocation(items);
|
const response = await ProjectRepository.manageProjectAllocation(payload);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
onSuccess: (data, variables, context) => {
|
onSuccess: (data, variables, context) => {
|
||||||
@ -422,7 +422,7 @@ export const useManageProjectAllocation = ({
|
|||||||
queryKey: ["empListByProjectAllocated"],
|
queryKey: ["empListByProjectAllocated"],
|
||||||
});
|
});
|
||||||
queryClient.removeQueries({ queryKey: ["projectEmployees"] });
|
queryClient.removeQueries({ queryKey: ["projectEmployees"] });
|
||||||
if (variables?.added) {
|
if (variables.actionType === "assign") {
|
||||||
showToast("Employee Assigned Successfully", "success");
|
showToast("Employee Assigned Successfully", "success");
|
||||||
} else {
|
} else {
|
||||||
showToast("Removed Employee Successfully", "success");
|
showToast("Removed Employee Successfully", "success");
|
||||||
|
@ -5,7 +5,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import ProjectOverview from "../../components/Project/ProjectOverview";
|
import ProjectOverview from "../../components/Project/ProjectOverview";
|
||||||
import AboutProject from "../../components/Project/AboutProject";
|
import AboutProject from "../../components/Project/AboutProject";
|
||||||
import ProjectNav from "../../components/Project/ProjectNav";
|
import ProjectNav from "../../components/Project/ProjectNav";
|
||||||
import Teams from "../../components/Project/Teams";
|
import Teams from "../../components/Project/Team/Teams";
|
||||||
import ProjectInfra from "../../components/Project/ProjectInfra";
|
import ProjectInfra from "../../components/Project/ProjectInfra";
|
||||||
import Loader from "../../components/common/Loader";
|
import Loader from "../../components/common/Loader";
|
||||||
import WorkPlan from "../../components/Project/WorkPlan";
|
import WorkPlan from "../../components/Project/WorkPlan";
|
||||||
|
@ -10,11 +10,33 @@ const OrganizationRepository = {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
getOrganizationBySPRID :(sprid)=>api.get(`/api/Organization/list?sprid=${sprid}`),
|
getOrganizationBySPRID: (sprid) =>
|
||||||
|
api.get(`/api/Organization/list?sprid=${sprid}`),
|
||||||
|
|
||||||
assignOrganizationToProject:(data)=>api.post(`/api/Organization/assign/project`,data),
|
assignOrganizationToProject: (data) =>
|
||||||
|
api.post(`/api/Organization/assign/project`, data),
|
||||||
|
|
||||||
assignOrganizationToTenanat:(organizationId)=>api.post(`/api/Organization/assign/tenant/${organizationId}`)
|
assignOrganizationToTenanat: (organizationId) =>
|
||||||
|
api.post(`/api/Organization/assign/tenant/${organizationId}`),
|
||||||
|
|
||||||
|
getOrganizationEmployees: (projectId, organizationId, searchString) => {
|
||||||
|
let url = `/api/Employee/list/organizations/${projectId}`;
|
||||||
|
const queryParams = [];
|
||||||
|
|
||||||
|
if (organizationId) {
|
||||||
|
queryParams.push(`organizationId=${organizationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchString) {
|
||||||
|
queryParams.push(`searchString=${encodeURIComponent(searchString)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryParams.length > 0) {
|
||||||
|
url += `?${queryParams.join("&")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.get(url);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default OrganizationRepository;
|
export default OrganizationRepository;
|
||||||
|
@ -5,19 +5,21 @@ const ProjectRepository = {
|
|||||||
getProjectByprojectId: (projetid) =>
|
getProjectByprojectId: (projetid) =>
|
||||||
api.get(`/api/project/details/${projetid}`),
|
api.get(`/api/project/details/${projetid}`),
|
||||||
|
|
||||||
getProjectAllocation: (projectId, organizationId, serviceId) => {
|
getProjectAllocation: (projectId, serviceId, organizationId, employeeStatus) => {
|
||||||
let url = `/api/project/allocation/${projectId}`;
|
let url = `/api/project/allocation/${projectId}`;
|
||||||
|
|
||||||
const params = [];
|
const params = [];
|
||||||
if (organizationId) params.push(`organizationId=${organizationId}`);
|
if (organizationId) params.push(`organizationId=${organizationId}`);
|
||||||
if (serviceId) params.push(`serviceId=${serviceId}`);
|
if (serviceId) params.push(`serviceId=${serviceId}`);
|
||||||
|
if (employeeStatus !== undefined) params.push(`includeInactive=${employeeStatus}`);
|
||||||
|
|
||||||
if (params.length > 0) {
|
if (params.length > 0) {
|
||||||
url += `?${params.join("&")}`;
|
url += `?${params.join("&")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return api.get(url);
|
||||||
|
},
|
||||||
|
|
||||||
return api.get(url);
|
|
||||||
},
|
|
||||||
|
|
||||||
getEmployeesByProject: (projectId) =>
|
getEmployeesByProject: (projectId) =>
|
||||||
api.get(`/api/Project/employees/get/${projectId}`),
|
api.get(`/api/Project/employees/get/${projectId}`),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user