340 lines
12 KiB
JavaScript
340 lines
12 KiB
JavaScript
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
|
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) return [];
|
|
|
|
let filtered = projectEmployees;
|
|
|
|
if (activeEmployee) {
|
|
filtered = projectEmployees.filter((emp) => !emp.isActive);
|
|
}
|
|
|
|
// Apply search filter if present
|
|
if (searchTerm?.trim()) {
|
|
const lower = searchTerm.toLowerCase();
|
|
filtered = filtered.filter((emp) => {
|
|
const fullName = `${emp.firstName ?? ""} ${emp.lastName ?? ""}`.toLowerCase();
|
|
const jobRole = getJobRole(emp?.jobRoleId)?.toLowerCase();
|
|
return fullName.includes(lower) || jobRole.includes(lower);
|
|
});
|
|
}
|
|
|
|
return filtered;
|
|
}, [projectEmployees, searchTerm, activeEmployee]);
|
|
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" : "In-active 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;
|