Compare commits

...

14 Commits

Author SHA1 Message Date
Pramod Mahajan
4b3f8cc50f Merge branch 'Ashutosh_Enhancement#132_Int_To_Guid' of https://git.marcoaiot.com/admin/marco.pms.web into Ashutosh_Enhancement#132_Int_To_Guid 2025-05-05 09:51:35 +05:30
Pramod Mahajan
8de7b4b5c3 hide dependent fields until parent selection; handle undefined project ID in useProjectDetails 2025-05-04 23:00:32 +05:30
Pramod Mahajan
498665c811 conditional rendering for building/floor/work area dropdowns 2025-05-04 22:57:27 +05:30
Pramod Mahajan
9ef09f9536 added filter for project cards list view 2025-05-04 19:58:28 +05:30
Pramod Mahajan
04a25d58f4 added condition for manageInfr for edit and delete activity 2025-05-04 19:41:59 +05:30
Pramod Mahajan
148f4b79e7 added loading indicator for project employee list during API call 2025-05-04 19:38:32 +05:30
Pramod Mahajan
8607dbb49b replaced isModalOpen with modalopen for modal visibility control 2025-05-04 19:25:11 +05:30
Pramod Mahajan
8be60953ba removed unuse code - useModal 2025-05-04 19:09:25 +05:30
Pramod Mahajan
4055a4532c updated message when no employees are available
,hide search and "Assign to Project" button while loading, show loader
2025-05-04 19:03:55 +05:30
Pramod Mahajan
4b9f5d737a removed unused code 2025-05-04 18:59:18 +05:30
Pramod Mahajan
e86d43c6e9 show loading spinner when modifying a project from the dropdown
Previously, clicking "Modify" in the project card dropdown would close the dropdown with no immediate feedback while the API call was in progress. Now, a loading spinner is shown to indicate that the modal is being prepared.
2025-05-04 18:58:39 +05:30
Pramod Mahajan
a96fa8efc7 show loading... initially instead of "No projects found" message at ui 2025-05-04 18:53:34 +05:30
Pramod Mahajan
294c13878d show delete spinner only for the employee being removed 2025-05-04 18:50:53 +05:30
Pramod Mahajan
b3906f460e filxed filtering project by project status 2025-05-04 18:41:46 +05:30
8 changed files with 192 additions and 84 deletions

View File

@ -104,7 +104,8 @@ const TaskModel = ({
}));
};
const onSubmitForm = async (data) => {
const onSubmitForm = async ( data ) =>
{
setIsSubmitting(true);
await onSubmit(data);
setValue("plannedWork", 0);
@ -114,9 +115,9 @@ const TaskModel = ({
const resetForm = () => {
setFormData(defaultModel);
setSelectedBuilding("0");
setSelectedFloor("0");
setSelectedWorkArea("0");
setSelectedBuilding(null); // not "0"
setSelectedFloor(null);
setSelectedWorkArea(null);
setSelectedActivity(null);
reset(defaultModel);
};
@ -255,10 +256,11 @@ const TaskModel = ({
onChange={handleActivityChange}
>
<option value="0">Select Activity</option>
{activityData && activityData.length > 0 ? (
{activityData && activityData.length > 0 && (
activityData
?.slice()
?.sort((a, b) => {
?.sort( ( a, b ) =>
{
const nameA = a?.activityName || "";
const nameB = b?.activityName || "";
return nameA.localeCompare( nameB );
@ -271,9 +273,11 @@ const TaskModel = ({
`Unnamed (id: ${ activity.id })`}
</option>
) )
) : (
) }
{(!loading && activities.length === 0 )&& (
<option disabled>No activities available</option>
)}
{loading && ( <option disabled>Loading...</option>)}
</select>
{errors.activityID && (
@ -337,8 +341,6 @@ const TaskModel = ({
<button type="submit" className="btn btn-sm btn-primary me-3">
{isSubmitting
? "Please Wait.."
: formData.id !== "0" && formData.id !== ""
? "Edit Task"
: "Add Task"}
</button>
<button

View File

@ -140,7 +140,7 @@ const WorkItem = ({ workItem, forBuilding, forFloor, forWorkArea }) => {
</span>
</button>
)}
{projectId && ManageInfra && (
{ ManageInfra && (
<>
<button
aria-label="Modify"

View File

@ -11,6 +11,8 @@ const MapUsers = ({
empJobRoles,
onSubmit,
allocation,
assignedLoading,
setAssignedLoading,
}) => {
const { employeesList, loading: employeeLoading, error } = useAllEmployees();
const [selectedEmployees, setSelectedEmployees] = useState([]);
@ -94,17 +96,19 @@ const MapUsers = ({
});
};
const handleSubmit = () => {
const handleSubmit = () =>
{
setAssignedLoading(true)
const selected = selectedEmployees
.filter((emp) => emp.isSelected)
.map((emp) => ({ empID: emp.id, jobRoleId: emp.jobRoleId }));
if (selected.length > 0) {
console.log(selected);
onSubmit(selected);
setSelectedEmployees([]);
} else {
showToast("Please select Employee", "error");
}
};
return (
<>
@ -112,14 +116,17 @@ const MapUsers = ({
<div className="modal-content">
<div className="modal-header">
<div className="md-2 mb-1">
{(filteredData.length > 0 ||
allocationEmployeesData.length > 0)&& (
<div className="input-group">
<input
type="search"
className="form-control form-control-sm"
placeholder="Search employees.."
placeholder="Search employees..."
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
)}
</div>
</div>
<div className="modal-body p-sm-4 p-0">
@ -131,19 +138,22 @@ const MapUsers = ({
>
<thead></thead>
<tbody>
{(employeeLoading && allocationEmployeesData.length === 0 ) && <p>Loading...</p>}
{employeeLoading && allocationEmployeesData.length === 0 && (
<p>Loading...</p>
)}
{!employeeLoading &&
allocationEmployeesData.length === 0 &&
filteredData.length === 0 && <p>No employees available.</p>}
{!employeeLoading &&
allocationEmployeesData.length > 0 &&
filteredData.length === 0 && (
<p>All employee assigned to Project.</p>
)}
{!employeeLoading && allocationEmployeesData.length > 0 && filteredData.length === 0 && (
<p>No matching employees found.</p>
)}
{filteredData.length > 0 &&
{(filteredData.length > 0 ||
allocationEmployeesData.length > 0) &&
filteredData.map((emp) => (
<AssignEmployeeTable
key={emp.id}
@ -158,9 +168,16 @@ const MapUsers = ({
</table>
</div>
<div className="modal-footer">
<button className="btn btn-sm btn-success" onClick={handleSubmit}>
Assign to Project
{(filteredData.length > 0 ||
allocationEmployeesData.length > 0) && (
<button
className="btn btn-sm btn-success"
onClick={handleSubmit}
>
{assignedLoading ? "Please Wait...":"Assign to Project"}
</button>
)}
<button
type="button"
className="btn btn-sm btn-secondary"

View File

@ -9,7 +9,10 @@ import { cacheData, getCachedData } from "../../slices/apiDataManager";
import showToast from "../../services/toastService";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { MANAGE_PROJECT } from "../../utils/constants";
import { getProjectStatusColor,getProjectStatusName } from "../../utils/projectStatus";
import {
getProjectStatusColor,
getProjectStatusName,
} from "../../utils/projectStatus";
const ProjectCard = ({ projectData }) => {
const [projectInfo, setProjectInfo] = useState(projectData);
@ -17,13 +20,16 @@ const ProjectCard = ({ projectData }) => {
const [showModal, setShowModal] = useState(false);
const navigate = useNavigate();
const ManageProject = useHasUserPermission(MANAGE_PROJECT);
const [modifyProjectLoading, setMdifyProjectLoading] = useState(false);
const handleShow = async () => {
try {
setMdifyProjectLoading(true);
const response = await ProjectRepository.getProjectByprojectId(
projectInfo.id
);
setProjectDetails(response.data);
setMdifyProjectLoading(false);
setShowModal(true);
} catch (error) {
showToast("Failed to load project details", "error");
@ -39,7 +45,6 @@ const ProjectCard = ({ projectData }) => {
const handleClose = () => setShowModal(false);
const handleViewProject = () => {
navigate(`/projects/${projectData.id}`);
};
@ -64,7 +69,9 @@ const ProjectCard = ({ projectData }) => {
if (projects_list) {
const updatedProjectsList = projects_list.map((project) =>
project.id === projectInfo.id
? { ...project, ...response.data,
? {
...project,
...response.data,
// tenant: project.tenant
}
: project
@ -81,6 +88,7 @@ const ProjectCard = ({ projectData }) => {
}
};
return (
<>
{showModal && projectDetails && (
@ -133,6 +141,9 @@ const ProjectCard = ({ projectData }) => {
data-bs-toggle="dropdown"
aria-expanded="false"
>
{modifyProjectLoading? <div class="spinner-border spinner-border-sm text-secondary" role="status">
<span class="visually-hidden">Loading...</span>
</div> :
<i
className="bx bx-dots-vertical-rounded bx-sm text-muted"
data-bs-toggle="tooltip"
@ -140,7 +151,7 @@ const ProjectCard = ({ projectData }) => {
data-bs-placement="top"
data-bs-custom-class="tooltip-dark"
title="More Action"
></i>
></i>}
</button>
<ul className="dropdown-menu dropdown-menu-end">
<li>

View File

@ -21,7 +21,7 @@ const ProjectInfra = ({
eachSiteEngineer,
}) => {
const [expandedBuildings, setExpandedBuildings] = useState([]);
const { projects_Details,refetch, loading } = useProjectDetails(data.id);
const { projects_Details,refetch, loading } = useProjectDetails(data?.id);
const [project, setProject] = useState(projects_Details);
const [modalConfig, setModalConfig] = useState({ type: null, data: null });
const [isModalOpen, setIsModalOpen] = useState(false);

View File

@ -18,23 +18,30 @@ const Teams = ({ project }) => {
const [isModalOpen, setIsModelOpen] = useState(false);
const [error, setError] = useState("");
const [empJobRoles, setEmpJobRoles] = useState(null);
const [clearFormTrigger, setClearFormTrigger] = useState(false);
const [employees, setEmployees] = useState([]);
const [filteredEmployees, setFilteredEmployees] = useState([]);
const [ removingEmployeeId, setRemovingEmployeeId ] = useState( null );
const [ assignedLoading, setAssignedLoading ] = useState( false )
const [employeeLodaing,setEmployeeLoading] = useState(false)
const HasAssignUserPermission = useHasUserPermission(ASSIGN_TO_PROJECT);
const fetchEmployees = async () => {
try {
try
{
setEmployeeLoading(true)
// if (!empRoles) {
ProjectRepository.getProjectAllocation(project.id)
.then((response) => {
setEmployees(response.data);
setFilteredEmployees( response.data.filter( ( emp ) => emp.isActive ) );
setEmployeeLoading(false)
})
.catch((error) => {
console.error(error);
setError( "Failed to fetch data." );
setEmployeeLoading(false)
});
} catch (err) {
setError("Failed to fetch activities.");
@ -45,8 +52,9 @@ const Teams = ({ project }) => {
ProjectRepository.manageProjectAllocation(items)
.then((response) => {
showToast("Details updated successfully.", "success");
setClearFormTrigger(true);
fetchEmployees();
setRemovingEmployeeId( null );
setAssignedLoading(false)
})
.catch((error) => {
showToast(error.message, "error");
@ -54,6 +62,7 @@ const Teams = ({ project }) => {
};
const removeAllocation = (item) => {
setRemovingEmployeeId(item.id);
submitAllocations([
{
empID: item.employeeId,
@ -134,13 +143,13 @@ const Teams = ({ project }) => {
aria-hidden="true"
>
<MapUsers
projectId={project.id}
projectId={project?.id}
onClose={onModelClose}
empJobRoles={empJobRoles}
onSubmit={handleEmpAlicationFormSubmit}
allocation={employees}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
assignedLoading={assignedLoading}
setAssignedLoading={setAssignedLoading}
></MapUsers>
</div>
@ -181,7 +190,8 @@ const Teams = ({ project }) => {
</div>
</div>
<div className="table-responsive text-nowrap">
{employees && employees.length > 0 ? (
{employeeLodaing && (<p>Loading..</p>)}
{!employeeLodaing && employees && employees.length > 0 && (
<table className="table ">
<thead>
<tr>
@ -205,7 +215,8 @@ const Teams = ({ project }) => {
<div className="d-flex flex-column">
<a
href="#"
onClick={(e) => {
onClick={( e ) =>
{
e.preventDefault(); // Prevent default link behavior
window.location.href =
"/employee/" + item.employee.id;
@ -247,7 +258,13 @@ const Teams = ({ project }) => {
onClick={() => removeAllocation( item )}
>
{" "}
<i className="bx bx-trash me-1 text-danger"></i>{" "}
{removingEmployeeId === item.id ? <div
class="spinner-border spinner-border-sm text-primary"
role="status"
>
<span class="visually-hidden">Loading...</span>
</div> : <i className="bx bx-trash me-1 text-danger"></i>}
</button>
)}
{!item.isActive && <span>Not in project</span>}
@ -256,7 +273,8 @@ const Teams = ({ project }) => {
) )}
</tbody>
</table>
) : (
)}
{(!employeeLodaing && employees.length == 0 ) && (
<span>No employees assigned to the project</span>
)}
</div>
@ -267,4 +285,3 @@ const Teams = ({ project }) => {
};
export default Teams;

View File

@ -9,7 +9,7 @@ export const useProjects = () => {
const { profile } = useProfile();
const dispatch = useDispatch();
const [projects, setProjects] = useState([]);
const [loading, setLoading] = useState(false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const projects_cache = getCachedData("projectslist");
@ -39,6 +39,7 @@ export const useProjects = () => {
if (!projects.length) {
const filtered = filterProjects(projects_cache);
setProjects( filtered );
setLoading(false);
}
}
};

View File

@ -27,7 +27,12 @@ const ProjectList = () => {
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage] = useState(6);
const [searchTerm, setSearchTerm] = useState("");
const [selectedStatuses, setSelectedStatuses] = useState(["b74da4c2-d07e-46f2-9919-e75e49b12731","603e994b-a27f-4e5d-a251-f3d69b0498ba","ef1c356e-0fe0-42df-a5d3-8daee355492d","33deaef9-9af1-4f2a-b443-681ea0d04f81",]);
const [selectedStatuses, setSelectedStatuses] = useState([
"b74da4c2-d07e-46f2-9919-e75e49b12731",
"603e994b-a27f-4e5d-a251-f3d69b0498ba",
"ef1c356e-0fe0-42df-a5d3-8daee355492d",
"33deaef9-9af1-4f2a-b443-681ea0d04f81",
]);
const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal(false);
@ -41,8 +46,8 @@ const ProjectList = () => {
grouped[statusId].push(project);
});
const sortedGrouped = Object.keys(grouped)
.sort()
const sortedGrouped = selectedStatuses
.filter((statusId) => grouped[statusId])
.flatMap((statusId) =>
grouped[statusId].sort((a, b) =>
a.name.toLowerCase().localeCompare(b.name.toLowerCase())
@ -171,7 +176,7 @@ const ProjectList = () => {
data-bs-custom-class="tooltip"
title="Card View"
>
<i className="bx bx-grid-alt"></i>
<i className="bx bx-grid-alt bx-sm"></i>
</button>
<button
type="button"
@ -185,9 +190,52 @@ const ProjectList = () => {
data-bs-custom-class="tooltip"
title="List View"
>
<i className="bx bx-list-ul"></i>
<i className="bx bx-list-ul bx-sm"></i>
</button>
</div>
{!listView && (
<div className="dropdown ms-3">
<a
className="dropdown-toggle hide-arrow cursor-pointer"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i className="bx bx-filter bx-lg"></i>
</a>
<ul className="dropdown-menu p-2 text-capitalize">
{[
{
id: "b74da4c2-d07e-46f2-9919-e75e49b12731",
label: "Active",
},
{
id: "603e994b-a27f-4e5d-a251-f3d69b0498ba",
label: "On Hold",
},
{
id: "ef1c356e-0fe0-42df-a5d3-8daee355492d",
label: "Inactive",
},
{
id: "33deaef9-9af1-4f2a-b443-681ea0d04f81",
label: "Completed",
},
].map(({ id, label }) => (
<li key={id}>
<div className="form-check">
<input
className="form-check-input "
type="checkbox"
checked={selectedStatuses.includes(id)}
onChange={() => handleStatusChange(id)}
/>
<label className="form-check-label">{label}</label>
</div>
</li>
))}
</ul>
</div>
)}
</div>
<div>
@ -234,10 +282,22 @@ const ProjectList = () => {
</a>
<ul className="dropdown-menu p-2 text-capitalize">
{[
{ id: "b74da4c2-d07e-46f2-9919-e75e49b12731", label: "Active" },
{ id: "603e994b-a27f-4e5d-a251-f3d69b0498ba", label: "On Hold" },
{ id: "ef1c356e-0fe0-42df-a5d3-8daee355492d", label: "Inactive" },
{ id: "33deaef9-9af1-4f2a-b443-681ea0d04f81", label: "Completed" },
{
id: "b74da4c2-d07e-46f2-9919-e75e49b12731",
label: "Active",
},
{
id: "603e994b-a27f-4e5d-a251-f3d69b0498ba",
label: "On Hold",
},
{
id: "ef1c356e-0fe0-42df-a5d3-8daee355492d",
label: "Inactive",
},
{
id: "33deaef9-9af1-4f2a-b443-681ea0d04f81",
label: "Completed",
},
].map(({ id, label }) => (
<li key={id}>
<div className="form-check">