partially integrated react-query in project infra

This commit is contained in:
Pramod Mahajan 2025-07-02 01:57:02 +05:30
parent a02a33a247
commit 0c8ff7b28c
19 changed files with 760 additions and 1087 deletions

View File

@ -8,7 +8,7 @@ import WorkAreaModel from "../Project/Infrastructure/WorkAreaModel";
import TaskModel from "../Project/Infrastructure/TaskModel";
import ProjectRepository from "../../repositories/ProjectRepository";
import Breadcrumb from "../../components/common/Breadcrumb";
import {useProjectDetails, useProjects} from "../../hooks/useProjects";
import {useProjectDetails, useProjectInfra, useProjects} from "../../hooks/useProjects";
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
import {MANAGE_PROJECT_INFRA} from "../../utils/constants";
import {useDispatch, useSelector} from "react-redux";
@ -21,11 +21,12 @@ const InfraPlanning = () =>
{
const {profile: LoggedUser, refetch : fetchData} = useProfile()
const dispatch = useDispatch()
// const {projects,loading:project_listLoader,error:projects_error} = useProjects()
const selectedProject = useSelector((store)=>store.localVariables.projectId)
const {projectInfra, isLoading, error} = useProjectInfra( selectedProject )
const ManageInfra = useHasUserPermission( MANAGE_PROJECT_INFRA )
const {projects_Details, loading: project_deatilsLoader, error: project_error,refetch} = useProjectDetails( selectedProject )
const reloadedData = useSelector( ( store ) => store.localVariables.reload )
@ -45,12 +46,9 @@ const InfraPlanning = () =>
<div className="card-body" style={{ padding: "0.5rem" }}>
<div className="align-items-center">
<div className="row ">
{project_deatilsLoader && ( <p>Loading...</p> )}
{( !project_deatilsLoader && projects_Details?.buildings.length === 0 ) && ( <p>No Result Found</p> )}
{(!project_deatilsLoader && projects_Details?.buildings?.length > 0) && (<InfraTable buildings={projects_Details?.buildings} projectId={projects_Details.id}/>)}
{isLoading && ( <p>Loading...</p> )}
{( !isLoading && projectInfra.length === 0 ) && ( <p>No Result Found</p> )}
{(!isLoading && projectInfra?.length > 0) && (<InfraTable buildings={projectInfra} projectId={selectedProject}/>)}
</div>
</div>
</div>

View File

@ -1,16 +1,14 @@
import React, { useEffect, useState } from "react";
import moment from "moment";
import { getProjectStatusName } from "../../utils/projectStatus";
const AboutProject = ({ data }) => {
const [CurrentProject, setCurrentProject] = useState(data);
useEffect(() => {
setCurrentProject(data);
}, [data]);
import {useProjectDetails} from "../../hooks/useProjects";
import {useParams} from "react-router-dom";
const AboutProject = () => {
const {projectId} = useParams();
const {projects_Details,isLoading,error} = useProjectDetails(projectId)
return (
<>
{data && (
{projects_Details && (
<div className="card mb-6">
<div className="card-body">
<small className="card-text text-uppercase text-muted small">
@ -21,8 +19,8 @@ const AboutProject = ({ data }) => {
<i className="bx bx-check"></i>
<span className="fw-medium mx-2">Start Date:</span>{" "}
<span>
{data.startDate
? moment(data.startDate).format("DD-MMM-YYYY")
{projects_Details.startDate
? moment(projects_Details.startDate).format("DD-MMM-YYYY")
: "N/A"}
</span>
</li>
@ -30,31 +28,32 @@ const AboutProject = ({ data }) => {
<i className="bx bx-stop-circle"></i>{" "}
<span className="fw-medium mx-2">End Date:</span>{" "}
<span>
{data.endDate
? moment(data.endDate).format("DD-MMM-YYYY")
{projects_Details.endDate
? moment(projects_Details.endDate).format("DD-MMM-YYYY")
: "N/A"}
</span>
</li>
<li className="d-flex align-items-center mb-2">
<i className="bx bx-trophy"></i>
<span className="fw-medium mx-2">Status:</span>{" "}
<span>{getProjectStatusName(data.projectStatusId)}</span>
<span>{projects_Details?.projectStatus?.status
}</span>
</li>
<li className="d-flex align-items-center mb-4">
<i className="bx bx-user"></i>
<span className="fw-medium mx-2">Contact:</span>{" "}
<span>{data.contactPerson}</span>
<span>{projects_Details.contactPerson}</span>
</li>
<li className="d-flex flex-column align-items-start mb-4">
<div className="d-flex align-items-center">
<i className="bx bx-flag"></i>
<span className="fw-medium mx-2">Address:</span>
{data.projectAddress?.length <= 20 && (
<span>{data.projectAddress}</span>
{projects_Details.projectAddress?.length <= 20 && (
<span>{projects_Details.projectAddress}</span>
)}
</div>
{data.projectAddress?.length > 20 && (
<div className="ms-4 text-start">{data.projectAddress}</div>
{projects_Details.projectAddress?.length > 20 && (
<div className="ms-4 text-start">{projects_Details.projectAddress}</div>
)}
</li>
</ul>
@ -62,7 +61,7 @@ const AboutProject = ({ data }) => {
</div>
)}
{!data && <span>loading...</span>}
{isLoading && <span>loading...</span>}
</>
);
};

View File

@ -4,7 +4,8 @@ const Building = ({
toggleBuilding,
expandedBuildings,
getContent,
}) => {
} ) =>
{
return (
<React.Fragment key={building.id}>
<tr className="overflow-auto">
@ -20,7 +21,7 @@ const Building = ({
>
<div className="row table-responsive">
<h6 style={{ marginBottom: "0px", fontSize: "14px" }}>
{building.name} &nbsp;
{building.buildingName} &nbsp;
{expandedBuildings.includes(building.id) ? (
<i className="bx bx-chevron-down"></i>
) : (

View File

@ -1,14 +1,13 @@
import React, { useState, useEffect } from "react";
import React, { useEffect, useMemo } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import ProjectRepository from "../../../repositories/ProjectRepository";
import { useSelector } from "react-redux";
import { useProjectDetails } from "../../../hooks/useProjects";
import { getCachedData } from "../../../slices/apiDataManager";
import showToast from "../../../services/toastService";
import { useManageProjectInfra } from "../../../hooks/useProjects";
import useSelect from "../../common/useSelect";
// Zod validation schema
const buildingSchema = z.object({
Id: z.string().optional(),
name: z.string().min(1, "Building name is required"),
@ -18,142 +17,113 @@ const buildingSchema = z.object({
.max(160, "Description cannot exceed 160 characters"),
});
const BuildingModel = ({
project,
onClose,
onSubmit,
clearTrigger,
onClearComplete,
editingBuilding = null,
}) => {
const BuildingModel = ({ project, onClose, editingBuilding = null }) => {
const selectedProject = useSelector(
(store) => store.localVariables.projectId
);
const [buildings, setBuildings] = useState([]);
const projects_Details = getCachedData("projectInfo");
const [formData, setFormData] = useState({
id: "",
name: "",
description: "",
projectId: project?.id,
});
useEffect(() => {
if (clearTrigger) {
setFormData({ id: null, name: "", description: "", projectId: project.id });
onClearComplete();
} else if (editingBuilding) {
setFormData({ ...editingBuilding, projectId: project.id });
}
return () => {
setValue("name", "");
};
}, [clearTrigger, onClearComplete, editingBuilding, project?.id]);
const {
register,
handleSubmit,
formState: { errors },
setValue,
watch,
reset,
getValues,
} = useForm({
resolver: zodResolver(buildingSchema),
defaultValues: formData, // Set default values from formData state
});
const handleBuildingChange = (e) => {
const selectedBuilding = project.buildings.find(
(b) => b.id === +e.target.value
);
if (selectedBuilding) {
setFormData({ ...selectedBuilding, projectId: project.id });
setValue("name", selectedBuilding.name); // Update name field
setValue("description", selectedBuilding.description); // Update description field
} else {
setFormData({ id: null, name: "", description: "", projectId: project.id });
setValue("name", "");
setValue("description", "");
}
};
const onSubmitHandler = async (data) => {
if (String(data.Id) === "0") {
data.Id = null;
}
onSubmit({ ...data, projectId: project.id });
reset({
defaultValues: {
Id: "0",
name: "",
description: "",
},
});
if (data.Id !== null) {
showToast("Building updated successfully.", "success");
} else {
showToast("Building created successfully.", "success");
}
};
const watchedId = watch("Id");
const { mutate: ManageBuilding, isPending } = useManageProjectInfra({
onSuccessCallback: (data, variables) => {
showToast(
watchedId != "0"
? "Building updated Successfully"
: "Building created Successfully",
"success"
);
reset({ Id: "0", name: "", description: "" });
onClose?.();
},
});
const sortedBuildings = useMemo(() => {
return (project || [])
.filter((b) => b?.buildingName)
.sort((a, b) => a.buildingName.localeCompare(b?.buildingName));
}, [project]);
useEffect(() => {
if(projects_Details){
setBuildings(projects_Details.data?.buildings);
if (!watchedId || watchedId === "0") {
setValue("name", "");
setValue("description", "");
} else {
const selected = sortedBuildings.find((b) => String(b.id) === watchedId);
if (selected) {
setValue("name", selected.buildingName || "");
setValue("description", selected.description || "");
}
}, [projects_Details]);
}
}, [watchedId, sortedBuildings, setValue]);
useEffect(() => {
if (editingBuilding) {
reset({
Id: String(editingBuilding.id),
name: editingBuilding.name,
description: editingBuilding.description,
});
}
}, [editingBuilding]);
const onSubmitHandler = (data) => {
const payload = {
...data,
Id: data.Id === "0" ? null : data.Id,
projectId: selectedProject,
};
let infraObject = [
{
building: payload,
floor: null,
workArea: null,
},
];
ManageBuilding({ infraObject, projectId: selectedProject });
};
return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
<div className="modal-content">
<div className="modal-body">
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
onClick={() => {
onClose();
reset(); // Call reset here
}}
></button>
<h5 className="text-center mb-2">
Manage Buildings - {project?.name}
</h5>
<form onSubmit={handleSubmit(onSubmitHandler)} className="row g-2">
<h5 className="text-center mb-2">Manage Buildings </h5>
<div className="col-12">
<label className="form-label">Select Building</label>
<select
{...register("Id")}
className="select2 form-select form-select-sm"
onChange={(e) => {
handleBuildingChange(e);
}}
>
<option value="0">Add New Building</option>
{project?.buildings?.length > 0 ? (
project.buildings
.filter((building) => building?.name)
.sort((a, b) => {
const nameA = a.name || "";
const nameB = b.name || "";
return nameA?.localeCompare(nameB);
})
.map((building) => (
<option key={building.id} value={building.id}>
{building.name}
{sortedBuildings.length > 0 ? (
sortedBuildings.map((b) => (
<option key={b.id} value={b.id}>
{b.buildingName}
</option>
))
) : (
<option disabled>No buildings found</option>
)}
</select>
{errors.Id && (
<span className="danger-text">{errors.Id.message}</span>
)}
{errors.Id && <span className="danger-text">{errors.Id.message}</span>}
</div>
{/* Name */}
<div className="col-12">
<label className="form-label">
{formData.id ? "Rename Building Name" : "New Building Name"}
{watchedId !== "0" ? "Rename Building Name" : "New Building Name"}
</label>
<input
{...register("name")}
@ -165,47 +135,47 @@ const BuildingModel = ({
)}
</div>
{/* Description */}
<div className="col-12">
<label className="form-label">Description</label>
<textarea
{...register("description")}
maxLength="160"
rows="5"
maxLength="160"
className="form-control form-control-sm"
/>
{errors.description && (
<span className="danger-text">
{errors.description.message}
</span>
<span className="danger-text">{errors.description.message}</span>
)}
</div>
<div className="col-12 text-center">
<button type="submit" className="btn btn-sm btn-primary me-3">
{formData.id && getValues("name")
<button
type="submit"
className="btn btn-sm btn-primary me-3"
disabled={isPending}
>
{isPending
? "Please wait..."
: watchedId !== "0"
? "Edit Building"
: "Add Building"}
</button>
<button
type="reset"
className="btn btn-sm btn-label-secondary"
data-bs-dismiss="modal"
aria-label="Close"
disabled={isPending}
onClick={() => {
onClose();
reset(); // Call reset here
reset();
}}
>
Cancel
</button>
</div>
</form>
</div>
</div>
</div>
);
};
export default BuildingModel;

View File

@ -1,17 +1,15 @@
import React, { useState, useEffect } from "react";
import React, { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { useActivitiesMaster, useWorkCategoriesMaster } from "../../../hooks/masterHook/useMaster";
import { useProjectDetails } from "../../../hooks/useProjects";
import { useDispatch, useSelector } from "react-redux";
import ProjectRepository from "../../../repositories/ProjectRepository";
import {
cacheData,
clearCacheKey,
getCachedData,
} from "../../../slices/apiDataManager";
useActivitiesMaster,
useWorkCategoriesMaster,
} from "../../../hooks/masterHook/useMaster";
import { useManageTask } from "../../../hooks/useProjects";
import { cacheData, getCachedData } from "../../../slices/apiDataManager";
import { refreshData } from "../../../slices/localVariablesSlice";
import showToast from "../../../services/toastService";
@ -20,18 +18,13 @@ const taskSchema = z
activityID: z.string().min(1, "Activity is required"),
workCategoryId: z.string().min(1, "Work Category is required"),
plannedWork: z.number().min(1, "Planned Work must be greater than 0"),
completedWork: z.number().min( 0, "Completed Work must be greater than 0" ),
comment:z.string()
completedWork: z.number().min(0, "Completed Work must be ≥ 0"),
comment: z.string(),
})
.refine(
(data) =>
data.completedWork === undefined ||
data.completedWork <= data.plannedWork,
{
.refine((data) => data.completedWork <= data.plannedWork, {
message: "Completed Work cannot be greater than Planned Work",
path: ["completedWork"], // error will show next to this field
}
);
path: ["completedWork"],
});
const EditActivityModal = ({
workItem,
@ -39,29 +32,12 @@ const EditActivityModal = ({
building,
floor,
onClose,
}) => {
const selectedProject = useSelector(
(store) => store.localVariables.projectId
);
const defaultModel = {
activityID: 0,
workCategoryId: 0,
plannedWork: 0,
completedWork: 0,
comment:""
};
} ) =>
{
const { projects_Details, refetch } = useProjectDetails(selectedProject);
const [ActivityUnit, setActivityUnit] = useState("");
const { activities, loading, error } = useActivitiesMaster();
const { categories, categoryLoading, categoryError } =
useWorkCategoriesMaster();
const [formData, setFormData] = useState(defaultModel);
const { activities, loading: loadingActivities } = useActivitiesMaster();
const { categories, loading: loadingCategories } = useWorkCategoriesMaster();
const [selectedActivity, setSelectedActivity] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [activityData, setActivityData] = useState([]);
const [categoryData, setCategoryData] = useState([]);
const dispatch = useDispatch();
const {
register,
@ -73,359 +49,204 @@ const EditActivityModal = ({
watch,
} = useForm({
resolver: zodResolver(taskSchema),
defaultValues: defaultModel,
defaultValues: {
activityID: "",
workCategoryId: "",
plannedWork: 0,
completedWork: 0,
comment: "",
},
});
const { mutate: UpdateTask, isPending } = useManageTask({
onSuccessCallback: () => onClose?.()
});
const handleActivityChange = (e) => {
const selectedId = String(e.target.value);
const selected = activityData.find((a) => a.id === selectedId);
const activityID = watch("activityID");
const sortedActivities = useMemo(
() =>
[...(activities || [])].sort((a, b) =>
a.activityName?.localeCompare(b.activityName)
),
[activities]
);
const sortedCategories = useMemo(
() => [...(categories || [])].sort((a, b) => a.name?.localeCompare(b.name)),
[categories]
);
useEffect(() => {
reset({
activityID: String(
workItem?.workItem?.activityId || workItem?.activityId || ""
),
workCategoryId: String(
workItem?.workItem?.workCategoryId || workItem?.workCategoryId || ""
),
plannedWork:
workItem?.workItem?.plannedWork || workItem?.plannedWork || 0,
completedWork:
workItem?.workItem?.completedWork || workItem?.completedWork || 0,
comment: workItem?.workItem?.description || workItem?.description || "",
});
}, [workItem, activities]);
useEffect(() => {
const selected = activities?.find((a) => a.id === activityID);
setSelectedActivity(selected || null);
setValue("activityID", selectedId);
};
}, [activityID, activities]);
const handleCategoryChange = (e) => {
const selectedId = String(e.target.value);
const category = categoryData.find((b) => b.id === selectedId);
setSelectedCategory(category || null);
setValue("workCategoryId", selectedId);
};
const onSubmitForm = async ( data ) =>
const onSubmitForm = (data) =>
{
setIsSubmitting(true)
const updatedProject = { ...projects_Details };
const finalData = {
const payload = {
...data,
id: workItem?.workItem?.id ?? workItem?.id,
buildingID: building?.id,
floorId: floor?.id,
workAreaId: workArea?.id,
};
ProjectRepository.manageProjectTasks([finalData])
.then((response) => {
if (response?.data[0]) {
const { workItemId, workItem } = response.data[0];
let finalUpdatedWorkItem = null;
const newProject = {
...updatedProject,
buildings: updatedProject.buildings.map((building) =>
building.id === finalData.buildingID
? {
...building,
floors: building.floors.map((floor) =>
floor.id === finalData.floorId
? {
...floor,
workAreas: floor.workAreas.map((workArea) =>
workArea.id === workItem?.workAreaId
? {
...workArea,
workItems: (() => {
const exists = workArea.workItems.some(
(item) =>
String(
item?.workItem?.id ?? item?.id
) === String(finalData.id)
);
finalUpdatedWorkItem = workItem;
return exists
? workArea.workItems.map((item) =>
String(
item?.workItem?.id ?? item?.id
) === String(finalData.id)
? workItem
: item
)
: [...workArea.workItems, workItem];
})(),
UpdateTask([payload])
}
: workArea
),
}
: floor
),
}
: building
),
};
cacheData("projectInfo", {
projectId: newProject.id,
data: newProject,
});
resetForm();
dispatch( refreshData( true ) );
setIsSubmitting(false)
showToast("Activity Updated Successfully","success")
onClose();
}
})
.catch( ( error ) =>
{
setIsSubmitting(false)
const message = error.response.data.message || error.message || "Error Occured During Api Call"
showToast( message, "error" );
});
};
const resetForm = () => {
setFormData(defaultModel);
setSelectedActivity(null);
reset(defaultModel);
};
useEffect(() => {
reset({
activityID: workItem?.workItem?.activityId || workItem?.activityId || 0,
workCategoryId:
workItem?.workItem?.workCategoryId || workItem?.workCategoryId || 0,
plannedWork:
workItem?.workItem?.plannedWork || workItem?.plannedWork || 0,
completedWork:
workItem?.workItem?.completedWork || workItem?.completedWork || 0,
comment:
workItem?.workItem?.description || workItem?.description || ""
});
return () => reset();
}, [activities, workItem]);
const ISselectedActivity = watch("activityID");
useEffect(() => {
if (ISselectedActivity) {
const selected = activities.find((a) => a.id === ISselectedActivity);
setSelectedActivity(selected || null);
setActivityUnit(selected?.unitOfMeasurement);
}
}, [ISselectedActivity, activities]);
return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
<div className="modal-content">
<div className="modal-body">
<div className="row">
<button
type="button"
className="btn-close"
aria-label="Close"
onClick={onClose}
/>
<form className="row g-2 p-2 p-md-1" onSubmit={handleSubmit(onSubmitForm)}>
<div className="text-center mb-1">
<h5 className="mb-1">Manage Task</h5>
</div>
<form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
{/* Select Building */}
<div className="col-6 col-md-6">
<label className="form-label" htmlFor="buildingID">
Select Building
</label>
<div className="row g-2">
<div className="col-12 col-md-6">
<label className="form-label">Select Building</label>
<input
type="text"
className="form-control form-control-sm"
value={building?.name}
value={building?.buildingName}
disabled
/>
</div>
{/* Select Floor */}
<div className="col-6 col-md-6">
<label className="form-label" htmlFor="floorId">
Select Floor
</label>
<div className="col-12 col-md-6">
<label className="form-label">Select Floor</label>
<input
type="text"
className="form-control form-control-sm"
value={floor?.floorName}
disabled
/>
</div>
</div>
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="workAreaId">
Select Work Area
</label>
<div className="col-12">
<label className="form-label">Select Work Area</label>
<input
type="text"
className="form-control form-control-sm"
value={workArea?.areaName}
disabled
/>
</div>
{/* Select Activity */}
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="activityID">
Select Activity
</label>
<div className="col-12">
<label className="form-label">Select Activity</label>
<select
id="activityID"
className="form-select form-select-sm"
{...register("activityID")}
className="form-select form-select-sm"
>
{loading ? (
<option value="">Loading...</option>
) : (
<option disabled>Select Activity</option>
)}
{activities &&
activities.length > 0 &&
activities
.slice()
.sort((a, b) =>
(a.activityName || "")?.localeCompare(
b.activityName || ""
)
)
.map((activity) => (
<option key={activity.id} value={activity.id}>
{activity.activityName}
{loadingActivities ? (
<option>Loading...</option>
) : (
sortedActivities.map((a) => (
<option key={a.id} value={a.id}>
{a.activityName}
</option>
))}
{!loading && activities.length === 0 && (
<option disabled>No activities available</option>
))
)}
</select>
{errors.activityID && (
<p className="danger-text">{errors.activityID.message}</p>
)}
</div>
{/* Select Category */}
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="activityID">
Select Work Category
</label>
<div className="col-12">
<label className="form-label">Select Work Category</label>
<select
id="workCategoryId"
className="form-select form-select-sm"
{...register("workCategoryId")}
className="form-select form-select-sm"
>
{loading ? (
<option value="">Loading...</option>
) : (
<option disabled>Select Category</option>
)}
{categories &&
categories.length > 0 &&
categories
.slice()
.sort((a, b) =>
(a.name || "")?.localeCompare(
b.name || ""
)
)
.map((category) => (
<option key={category.id} value={category.id}>
{category.name}
{loadingCategories ? (
<option>Loading...</option>
) : (
sortedCategories.map((c) => (
<option key={c.id} value={c.id}>
{c.name}
</option>
))}
{!loading && categories.length === 0 && (
<option disabled>No categories available</option>
))
)}
</select>
{errors.workCategoryId && (
<p className="danger-text">{errors.workCategoryId.message}</p>
)}
</div>
{/* Planned Work */}
{/* {ISselectedActivity && ( */}
<div className="col-5 col-md-5">
<label className="form-label" htmlFor="plannedWork">
Planned Work
</label>
<div className="col-5">
<label className="form-label">Planned Work</label>
<input
{...register("plannedWork", { valueAsNumber: true })}
type="number"
className="form-control form-control-sm me-2"
placeholder="Planned Work"
className="form-control form-control-sm"
/>
{errors.plannedWork && (
<p className="danger-text">{errors.plannedWork.message}</p>
)}
</div>
{/* )} */}
{/* Completed Work */}
{/* {ISselectedActivity && ( */}
<div className="col-5 col-md-5">
<label className="form-label" htmlFor="completedWork">
Completed Work
</label>
<div className="col-5">
<label className="form-label">Completed Work</label>
<input
{...register("completedWork", { valueAsNumber: true })}
type="number"
className="form-control form-control-sm me-2"
placeholder="Completed Work"
disabled={getValues("completedWork") > 0}
className="form-control form-control-sm"
/>
{errors.completedWork && (
<p className="danger-text">{errors.completedWork.message}</p>
)}
</div>
{/* )} */}
{/* Unit */}
{/* {ISselectedActivity && ( */}
<div className="col-2 col-md-2">
<label className="form-label" htmlFor="unit">
Unit
</label>
<div className="col-2">
<label className="form-label">Unit</label>
<input
type="text"
className="form-control form-control-sm"
disabled
className="form-control form-control-sm me-2"
value={selectedActivity?.unitOfMeasurement || ""}
/>
</div>
{/* )} */}
<div className="col-12">
<label
className="form-text fs-7 m-1 text-lg text-dark"
htmlFor="descriptionTextarea"
>
Comment
</label>
<textarea
{...register("comment")}
className="form-control"
id="descriptionTextarea"
rows="2"
/>
<label className="form-label">Comment</label>
<textarea {...register("comment")} rows="2" className="form-control" />
{errors.comment && (
<div className="danger-text">
{errors.comment.message}
</div>
<div className="danger-text">{errors.comment.message}</div>
)}
</div>
<div className="col-12 text-center">
<button type="submit" className="btn btn-sm btn-primary me-3" disabled={activities.length === 0 || isSubmitting}>
{isSubmitting ? "Please Wait.." : "Edit Task"}
<button
type="submit"
className="btn btn-sm btn-primary me-2"
disabled={isPending}
>
{isPending ? "Please Wait..." : "Edit Task"}
</button>
<button
type="button"
className="btn btn-sm btn-label-secondary"
onClick={onClose}
disabled={isPending}
>
Cancel
</button>
</div>
</form>
</div>
</div>
</div>
</div>
);
};

View File

@ -16,13 +16,7 @@ const Floor = ({ floor, workAreas, forBuilding }) => {
<tr>
<td colSpan="4" className="text-start table-cell">
<div className="row ps-2">
{/* <div className="col-1 col-md-1 d-flex justify-content-between align-items-center " >
<button
className="btn me-2"
>
</button>
</div> */}
<div className="col-12 ps-8">
<div className="row">
<div className="d-flex col-5">

View File

@ -1,236 +1,190 @@
import React, { useState, useEffect } from "react";
import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import showToast from "../../../services/toastService";
import { useManageProjectInfra } from "../../../hooks/useProjects";
import { useSelector } from "react-redux";
// Schema
const floorSchema = z.object({
buildingId: z
.string()
.refine((val) => val !== "0", {
message: "Building is required",
}),
.refine((val) => val !== "0", { message: "Building is required" }),
id: z.string().optional(),
floorName: z.string().min(1, "Floor Name is required"),
});
const defaultModel = {
const defaultValues = {
id: "0",
floorName: "",
buildingId: "0",
};
const FloorModel = ({
project,
onClose,
onSubmit,
clearTrigger,
onClearComplete,
}) => {
const [formData, setFormData] = useState(defaultModel);
const [selectedBuilding, setSelectedBuilding] = useState({});
const [buildings, setBuildings] = useState(project?.buildings || []);
const FloorModel = ({ project, onClose, onSubmit }) => {
const selectedProject = useSelector(
(store) => store.localVariables.projectId
);
const [selectedBuilding, setSelectedBuilding] = useState(null);
const {
register,
handleSubmit,
setValue,
reset,
watch,
formState: { errors },
} = useForm({
defaultValues,
resolver: zodResolver(floorSchema),
defaultValues: defaultModel,
});
const watchId = watch("id");
const watchBuildingId = watch("buildingId");
const { mutate: ManageFloor, isPending } = useManageProjectInfra({
onSuccessCallback: (data, variables) => {
showToast(
watchId != "0"
? "Floor updated Successfully"
: "Floor created Successfully",
"success"
);
reset({ Id: "0", name: "", description: "" });
onClose?.();
},
});
useEffect(() => {
if (clearTrigger) {
reset(defaultModel);
onClearComplete();
}
}, [clearTrigger, onClearComplete, reset]);
reset(defaultValues);
}, []);
useEffect(() => {
const building = project?.find((b) => b.id === watchBuildingId);
setSelectedBuilding(building || null);
}, [watchBuildingId, project]);
const handleBuildigChange = (e) => {
const buildingId = e.target.value;
const building = buildings.find((b) => b.id === String(buildingId));
if (building) {
setSelectedBuilding(building);
setFormData({
id: "",
floorName: "",
buildingId: building.id,
});
setValue("buildingId", building.id, { shouldValidate: true }); // trigger validation
const handleBuildingChange = (e) => {
const id = e.target.value;
setValue("buildingId", id, { shouldValidate: true });
setValue("id", "0");
} else {
setSelectedBuilding({});
setFormData({
id: "",
floorName: "",
buildingId: "0",
});
setValue("buildingId", "0", { shouldValidate: true }); // trigger validation
}
setValue("floorName", "");
};
const handleFloorChange = (e) => {
const id = e.target.value;
const floor = selectedBuilding.floors?.find((b) => b.id === String(id));
setValue("id", id);
const floor = selectedBuilding?.floors?.find((f) => f.id === id);
if (floor) {
setFormData({
id: floor.id,
floorName: floor.floorName,
buildingId: selectedBuilding.id,
});
setValue("floorName", floor.floorName);
} else {
setFormData({
id: "0",
floorName: "",
buildingId: selectedBuilding.id,
});
setValue("floorName", "");
}
};
const onFormSubmit = (data) => {
if (data.id === "0") {
data.id = null;
}
const isEdit = data.id !== "0";
const payload = {
...data,
id: isEdit ? data.id : null,
};
let infraObject = [
{
building: null,
floor: payload,
workArea: null,
},
];
onSubmit(data);
reset({ floorName: "" });
if (data.id !== null) {
showToast("Floor updated successfully.", "success");
} else {
showToast("Floor created successfully.", "success");
}
ManageFloor({ infraObject, projectId: selectedProject });
};
return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
<div className="modal-content">
<div className="modal-body">
<div className="row">
<button
type="button"
className="btn-close"
aria-label="Close"
onClick={onClose}
/>
<div className="text-center mb-1">
<h5 className="mb-1">Manage Floors - {project.name}</h5>
</div>
<form className="row g-2" onSubmit={handleSubmit(onFormSubmit)}>
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="buildingId">
Select Building
</label>
<div className="text-center mb-1">
<h5 className="mb-1">Manage Floor</h5>
</div>
<div className="col-12">
<label className="form-label">Select Building</label>
<select
id="buildingId"
className="select2 form-select form-select-sm"
aria-label="Select Building"
{...register("buildingId")}
onChange={handleBuildigChange}
className="form-select form-select-sm"
onChange={handleBuildingChange}
>
<option value="0">Select Building</option>
{buildings?.length > 0 &&
buildings
.filter((building) => building?.name)
.sort((a, b) =>
(a.name || "")?.localeCompare(b.name || "")
)
.map((building) => (
<option key={building.id} value={building.id}>
{building.name}
{project?.length > 0 &&
project
.filter((b) => b.buildingName)
.sort((a, b) => a.buildingName.localeCompare(b.buildingName))
.map((b) => (
<option key={b.id} value={b.id}>
{b.buildingName}
</option>
))}
{buildings?.length === 0 && (
<option disabled>No buildings found</option>
)}
</select>
{errors.buildingId && (
<p className="text-danger">{errors.buildingId.message}</p>
)}
</div>
{formData.buildingId !== "0" && (
{watchBuildingId !== "0" && (
<>
<div className="col-12 col-md-12">
<div className="col-12">
<label className="form-label">Select Floor</label>
<select
id="id"
className="select2 form-select form-select-sm"
aria-label="Select Floor"
{...register("id")}
className="form-select form-select-sm"
onChange={handleFloorChange}
>
<option value="0">Add New Floor</option>
{selectedBuilding?.floors?.length > 0 &&
[...selectedBuilding.floors]
.filter((floor) => floor?.floorName)
.sort((a, b) =>
(a.floorName || "")?.localeCompare(
b.floorName || ""
)
)
.map((floor) => (
<option key={floor.id} value={floor.id}>
{floor.floorName}
selectedBuilding.floors
.filter((f) => f.floorName)
.sort((a, b) => a.floorName.localeCompare(b.floorName))
.map((f) => (
<option key={f.id} value={f.id}>
{f.floorName}
</option>
))}
{selectedBuilding?.floors?.length === 0 && (
<option disabled>No floors found</option>
)}
</select>
{errors.id && (
<p className="text-danger">{errors.id.message}</p>
)}
</div>
<div className="col-12 col-md-12">
<div className="col-12">
<label className="form-label">
{formData.id !== "0" ? "Modify " : "Enter "} Floor Name
{watchId !== "0" ? "Edit Floor Name" : "New Floor Name"}
</label>
<input
type="text"
id="floorName"
className="form-control form-control-sm me-2"
placeholder="Floor Name"
{...register("floorName")}
className="form-control form-control-sm"
placeholder="Floor Name"
/>
{errors.floorName && (
<p className="text-danger">
{errors.floorName.message}
</p>
<p className="text-danger">{errors.floorName.message}</p>
)}
</div>
</>
)}
<div className="col-12 text-center">
<button type="submit" className="btn btn-sm btn-primary me-3">
{formData.id !== "0" && formData.id !== ""
<button
type="submit"
className="btn btn-sm btn-primary me-3"
disabled={isPending}
>
{isPending
? "Please Wait"
: watchId !== "0"
? "Edit Floor"
: "Add Floor"}
</button>
<button
type="button"
className="btn btn-sm btn-label-secondary"
data-bs-dismiss="modal"
aria-label="Close"
disabled={isPending}
onClick={onClose}
>
Cancel
</button>
</div>
</form>
</div>
</div>
</div>
</div>
);
};

View File

@ -100,14 +100,7 @@ const InfraTable = ({ buildings, projectId, signalRHandler }) => {
No floors have been added yet. Start by adding floors to manage
this building.
</p>
{/* <button
type="button"
className="btn btn-xs btn-primary"
onClick={() => handleAddFloor(building)}
>
<i className="bx bx-plus-circle me-2"></i>
Add Floors
</button> */}
</div>
</td>
</tr>

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState } from "react";
import WorkItem from "./WorkItem";
import { useProjectDetails } from "../../../hooks/useProjects";
import { cacheData, getCachedData } from "../../../slices/apiDataManager";
import { useProjectDetails, useProjectTasks } from "../../../hooks/useProjects";
import { cacheData } from "../../../slices/apiDataManager";
import { useDispatch, useSelector } from "react-redux";
import { refreshData } from "../../../slices/localVariablesSlice";
import ProjectRepository from "../../../repositories/ProjectRepository";
@ -13,89 +13,67 @@ import {
MANAGE_TASK,
} from "../../../utils/constants";
import { useParams } from "react-router-dom";
import ProgressDonutChart from "../../Charts/ProgressDonutChart";
import ProgressBar from "../../common/ProgressBar";
import { componentsToColor } from "pdf-lib";
const WorkArea = ( {workArea, floor, forBuilding} ) =>
{
const selectedProject = useSelector( ( store ) => store.localVariables.projectId )
const { projects_Details, loading, error, refetch } = useProjectDetails(
selectedProject
);
const [workItems, setWorkItems] = useState([]);
const WorkArea = ({ workArea, floor, forBuilding }) => {
const selectedProject = useSelector((store) => store.localVariables.projectId);
const { projects_Details, loading } = useProjectDetails(selectedProject);
const [IsExpandedArea, setIsExpandedArea] = useState(false);
const dispatch = useDispatch();
const [Project, setProject] = useState();
const { projectId } = useParams();
const ManageTasks = useHasUserPermission(MANAGE_TASK);
const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
const ManageAndAssignTak = useHasUserPermission(ASSIGN_REPORT_TASK);
const { ProjectTaskList, isLoading } = useProjectTasks(workArea.id, IsExpandedArea);
const [workAreaStatus, setWorkAreaStatus] = useState({
completed: 0,
planned: 100,
});
useEffect(() => {
const totalCompleted = workItems.reduce(
(sum, i) => sum + (i.workItem?.completedWork || 0),
0
);
const totalPlanned = workItems.reduce(
(sum, i) => sum + (i.workItem?.plannedWork || 0),
0
);
const percent =
totalPlanned > 0 ? (totalCompleted / totalPlanned) * 100 : 0;
//setPercentComplete(Math.min(percent, 100)); // cap at 100%
setWorkAreaStatus({ completed: totalCompleted, planned: totalPlanned });
}, [workItems]);
setProject(projects_Details);
}, [projects_Details]);
useEffect(() => {
// const project = getCachedData("projectInfo");
setProject( projects_Details );
if (!projects_Details || !forBuilding?.id || !floor?.id || !workArea?.id) return;
const building = projects_Details.buildings?.find((b) => b.id === forBuilding.id);
const floors = building?.floors?.find((f) => f.id === floor.id);
const workAreas = floor?.workAreas?.find((wa) => wa.id === workArea.id);
setWorkItems(workAreas?.workItems || []);
}, [workArea, floor, floor,loading]);
const totalCompleted = ProjectTaskList?.reduce(
(sum, i) => sum + (i?.workItem?.completedWork || 0),
0
);
const totalPlanned = ProjectTaskList?.reduce(
(sum, i) => sum + (i?.workItem?.plannedWork || 0),
0
);
setWorkAreaStatus({ completed: totalCompleted, planned: totalPlanned });
}, [ProjectTaskList]);
const HanldeDeleteActivity = async (workItemId) => {
try {
const updatedProject = { ...Project.data };
const response = await ProjectRepository.deleteProjectTask(workItemId);
await ProjectRepository.deleteProjectTask(workItemId);
const newProject = {
...updatedProject,
buildings: updatedProject?.buildings.map((building) =>
building?.id === building?.id
? {
buildings: updatedProject?.buildings.map((building) => ({
...building,
floors: building?.floors?.map((floor) =>
floor.id === floor?.id
? {
floors: building?.floors.map((floor) => ({
...floor,
workAreas: floor.workAreas.map((workArea) =>
workArea.id === workArea?.id
workAreas: floor.workAreas.map((wa) =>
wa.id === workArea.id
? {
...workArea,
workItems: workArea.workItems.filter(
...wa,
workItems: wa.workItems.filter(
(item) =>
String(item?.workItem?.id ?? item?.id) !==
String(workItemId)
),
}
: workArea
),
}
: floor
),
}
: building
: wa
),
})),
})),
};
cacheData("projectInfo", {
@ -104,131 +82,93 @@ const WorkArea = ( {workArea, floor, forBuilding} ) =>
});
dispatch(refreshData(true));
showToast("Activity Deleted Successfully", "success");
} catch (error) {
const message =
error.response?.data?.message ||
error.message ||
"An unexpected error occurred";
error.response?.data?.message || error.message || "An error occurred";
showToast(message, "error");
}
};
useEffect(() => {
const toggleButtons = document.querySelectorAll(".accordion-button");
const collapseElement = document.getElementById(`collapse-${workArea.id}`);
toggleButtons.forEach((btn) => {
const icon = btn.querySelector(".toggle-icon");
const handleShown = () => setIsExpandedArea(true);
const handleHidden = () => setIsExpandedArea(false);
btn.addEventListener("click", () => {
setTimeout(() => {
if (btn.classList.contains("collapsed")) {
icon.classList.remove("bx-minus-circle");
icon.classList.add("bx-plus-circle");
} else {
icon.classList.remove("bx-plus-circle");
icon.classList.add("bx-minus-circle");
}
}, 300); // allow Bootstrap collapse to complete
});
});
collapseElement?.addEventListener("shown.bs.collapse", handleShown);
collapseElement?.addEventListener("hidden.bs.collapse", handleHidden);
return () => {
toggleButtons.forEach((btn) => {
btn.removeEventListener("click", () => {});
});
collapseElement?.removeEventListener("shown.bs.collapse", handleShown);
collapseElement?.removeEventListener("hidden.bs.collapse", handleHidden);
};
}, []);
}, [workArea.id]);
return (
<React.Fragment key={workArea.id}>
<tr>
<td colSpan="4" className="p-0">
<div
className="accordion border-none"
id={`accordion-${workArea.id}`}
>
<div className="accordion border-none" id={`accordion-${workArea.id}`}>
<div className="accordion-item background border-0">
{/* Accordion Header */}
<p
className="accordion-header mb-0"
id={`heading-${workArea.id}`}
>
<p className="accordion-header mb-0" id={`heading-${workArea.id}`}>
<button
className={`accordion-button text-start px-2 py-2 custom-accordion-btn ${
workItems && workItems.length > 0 ? "collapsed" : "disabled"
}`}
className="accordion-button text-start px-2 py-2 custom-accordion-btn collapsed"
type="button"
data-bs-toggle={
workItems && workItems.length > 0 ? "collapse" : ""
}
data-bs-target={
workItems && workItems.length > 0
? `#collapse-${workArea.id}`
: undefined
}
data-bs-toggle="collapse"
data-bs-target={`#collapse-${workArea.id}`}
aria-expanded="false"
aria-controls={`collapse-${workArea.id}`}
disabled={!(workItems && workItems.length > 0)}
>
<i
className={`bx me-2 toggle-icon ${
workItems && workItems.length > 0
? "bx-plus-circle"
: "bx-block"
IsExpandedArea ? "bx-minus-circle" : "bx-plus-circle"
}`}
style={{
fontSize: "1.2rem",
color:
workItems && workItems.length > 0 ? "" : "transparent",
color: "black",
}}
></i>
<div className="d-flex justify-content-start row w-100 align-items-center">
<div className="d-flex col-5">
<span className="fw-semibold text-primary small">
Floor:
</span>
<span className="fw-semibold text-primary small">Floor:</span>
<span className="fw-normal text-darkgreen small px-2">
{floor.floorName}
</span>
</div>
<div className="text-start col-5">
<span className="fw-semibold text-primary small">
Work Area:
</span>
<span className="fw-semibold text-primary small">Work Area:</span>
<span className="fw-normal text-darkgreen small px-2">
{workArea.areaName}
</span>
</div>
{workArea?.workItems?.length > 0 && (
{ProjectTaskList?.length > 0 && (
<div className="col-2">
<ProgressBar
completedWork={workAreaStatus.completed}
plannedWork={workAreaStatus.planned}
className="m-0 text-info"
></ProgressBar>
/>
</div>
)}
</div>
</button>
</p>
{/* Accordion Body */}
{workItems && workItems.length > 0 && (
<div
id={`collapse-${workArea.id}`}
className="accordion-collapse collapse"
aria-labelledby={`heading-${workArea.id}`}
>
<div className="accordion-body px-1">
{isLoading ? (
<div className="text-center py-2 text-muted">Loading activities...</div>
) : ProjectTaskList?.length > 0 ? (
<table className="table table-sm mx-1">
<thead>
<tr>
<th className="infra-activity-table-header-first">
Activity
</th>
<th className="infra-activity-table-header-first">Activity</th>
<th className="infra-activity-table-header d-sm-table-cell d-md-none">
Status
</th>
@ -241,11 +181,8 @@ const WorkArea = ( {workArea, floor, forBuilding} ) =>
<th className="infra-activity-table-header d-none d-md-table-cell">
Today's Planned
</th>
<th className="infra-activity-table-header">
Progress
</th>
{(ManageInfra ||
(!projectId && ManageAndAssignTak)) && (
<th className="infra-activity-table-header">Progress</th>
{(ManageInfra || (!projectId && ManageAndAssignTak)) && (
<th className="infra-activity-table-header text-end">
<span className="px-2">Actions</span>
</th>
@ -253,7 +190,7 @@ const WorkArea = ( {workArea, floor, forBuilding} ) =>
</tr>
</thead>
<tbody className="table-border-bottom-0">
{workArea.workItems.map((workItem) => (
{ProjectTaskList.map((workItem) => (
<WorkItem
key={workItem.workItemId}
workItem={workItem}
@ -265,14 +202,19 @@ const WorkArea = ( {workArea, floor, forBuilding} ) =>
))}
</tbody>
</table>
</div>
) : (
<div className="text-center text-muted py-3">
No activities available for this work area.
</div>
)}
</div>
</div>
</div>
</div>
</td>
</tr>
</React.Fragment>
);
};
export default WorkArea;

View File

@ -79,8 +79,7 @@ const WorkItem = ({
tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el));
}, []);
const showModal1 = () => setShowModal(true);
const closeModal1 = () => setShowModal(false);
const showModalDelete = () => setShowModal2(true);
const closeModalDelete = () => setShowModal2(false);
@ -105,21 +104,15 @@ const WorkItem = ({
)}
{showModal && (
<div
className={`modal fade ${showModal ? "show" : ""}`}
tabIndex="-1"
role="dialog"
style={{ display: showModal ? "block" : "none" }}
aria-hidden={!showModal}
>
<GlobalModel isOpen={showModal} size="lg" closeModal={()=>setShowModal(false)}>
<EditActivityModal
workItem={workItem}
workArea={forWorkArea}
building={forBuilding}
floor={forFloor}
onClose={closeModal1}
onClose={()=>setShowModal(false)}
/>
</div>
</GlobalModel>
)}
{showModal2 && (
@ -169,7 +162,7 @@ const WorkItem = ({
: "NA"}
</td>
{/* Category - visible on medium and above */}
<td className="text-center table-cell-small d-none d-md-table-cell">
<span className="fw-light">
{hasWorkItem
@ -255,7 +248,7 @@ const WorkItem = ({
<i
className="bx bxs-edit text-secondary cursor-pointer"
title="Edit"
onClick={showModal1}
onClick={()=>setShowModal(true)}
role="button"
></i>
<i
@ -297,7 +290,7 @@ const WorkItem = ({
<li>
<a
className="dropdown-item d-flex align-items-center"
onClick={showModal1}
onClick={()=>setShowModal(true) }
>
<i className="bx bxs-edit text-secondary me-2"></i> Edit
</a>

View File

@ -17,42 +17,39 @@ import {
clearCacheKey,
getCachedData,
} from "../../slices/apiDataManager";
import { useProjectDetails } from "../../hooks/useProjects";
import { useProjectDetails, useProjectInfra } from "../../hooks/useProjects";
import { useDispatch, useSelector } from "react-redux";
import { refreshData } from "../../slices/localVariablesSlice";
import eventBus from "../../services/eventBus";
import {useParams} from "react-router-dom";
import GlobalModel from "../common/GlobalModel";
const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) =>
{
const {projectId} = useParams()
const reloadedData = useSelector((store) => store.localVariables.reload);
const [expandedBuildings, setExpandedBuildings] = useState([]);
const [ expandedBuildings, setExpandedBuildings ] = useState( [] );
const {projectInfra,isLoading,error} = useProjectInfra(projectId)
const { projects_Details, refetch, loading } = useProjectDetails(data?.id);
const [project, setProject] = useState(projects_Details);
const [ project, setProject ] = useState( projects_Details );
const [modalConfig, setModalConfig] = useState({ type: null, data: null });
const [isModalOpen, setIsModalOpen] = useState(false);
const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
const [isBuildingModalOpen, setIsBuildingModalOpen] = useState(false);
const [isFloorModalOpen, setIsFloorModalOpen] = useState(false);
const [showModalFloor, setshowModalFloor] = useState(false);
const [isWorkAreaModelOpen, setIsWorkAreaModalOpen] = useState(false);
const [isTaskModelOpen, setIsTaskModalOpen] = useState(false);
const [isAssignRoleModal, setIsAssingRoleModal] = useState(false);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [clearFormTrigger, setClearFormTrigger] = useState(false);
const [CurrentBuilding, setCurrentBuilding] = useState("");
const [showModal, setShowModal] = useState(false);
const [showModalBuilding, setshowModalBuilding] = useState(false);
const dispatch = useDispatch();
useEffect(() => {
setProject(projects_Details);
setProject(projectInfra);
}, [data, projects_Details]);
const openFloorModel = (projectData) => {
setIsFloorModalOpen(true);
};
const closeFloorModel = () => {
setIsFloorModalOpen(false);
};
const openAssignModel = (assignData) => {
setCurrentBuilding(assignData);
setIsAssingRoleModal(true);
@ -65,29 +62,6 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
setIsBuildingModalOpen(false);
};
const handleBuildingModelFormSubmit = (buildingmodel) => {
if (buildingmodel.id == "" || buildingmodel.id == 0)
delete buildingmodel.id;
let data = [
{
building: buildingmodel,
floor: null,
workArea: null,
},
];
submitData(data);
};
const handleFloorModelFormSubmit = (updatedFloor) => {
if (updatedFloor.id == "") delete updatedFloor.id;
submitData([
{
building: null,
floor: updatedFloor,
workArea: null,
},
]);
};
const openWorkAreaModel = (projectData) => {
setIsWorkAreaModalOpen(true);
};
@ -322,23 +296,20 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
const modalElement = document.getElementById("building-model");
if (modalElement) {
modalElement.classList.remove("show"); // Remove modal visibility class
modalElement.style.display = "none"; // Hide the modal element
modalElement.classList.remove("show");
modalElement.style.display = "none";
}
document.body.classList.remove("modal-open"); // Remove modal-open class from body
document.body.classList.remove("modal-open");
// Remove the modal backdrop
const backdropElement = document.querySelector(".modal-backdrop");
if (backdropElement) {
backdropElement.classList.remove("modal-backdrop"); // Remove backdrop class
backdropElement.style.display = "none"; // Hide the backdrop element
backdropElement.classList.remove("modal-backdrop");
backdropElement.style.display = "none";
}
document.body.style.overflow = "auto";
};
const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal(false);
useEffect(() => {
if (reloadedData) {
refetch();
@ -352,39 +323,21 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
return (
<>
<div
className={`modal fade ${showModal ? "show" : ""}`}
tabIndex="-1"
role="dialog"
style={{ display: showModal ? "block" : "none" }}
aria-hidden={!showModal}
>
{showModalBuilding && <GlobalModel isOpen={showModalBuilding} size="md" closeModal={() => setshowModalBuilding( false )}>
<BuildingModel
project={project}
onClose={handleClose}
onSubmit={handleBuildingModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
></BuildingModel>
</div>
{isFloorModalOpen && (
<div
className="modal fade show"
id="floor-model"
tabIndex="-1"
role="dialog"
style={{ display: "block" }}
aria-hidden="false"
>
<FloorModel
project={project}
onClose={closeFloorModel}
onSubmit={handleFloorModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
project={projectInfra}
onClose={() => setshowModalBuilding( false )}
/>
</div>
)}
</GlobalModel>}
{showModalFloor && <GlobalModel isOpen={showModalFloor} size="md" closeModal={()=>setshowModalFloor(false)}>
<FloorModel
project={projectInfra}
onClose={()=>setshowModalFloor(false)}
/>
</GlobalModel>}
{isWorkAreaModelOpen && (
<div
@ -398,8 +351,7 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
<WorkAreaModel
project={project}
onClose={closeWorkAreaModel}
onSubmit={handleWorkAreaModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
/>
</div>
@ -418,7 +370,7 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
project={project}
onClose={closeTaskModel}
onSubmit={handleTaskModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
/>
</div>
@ -441,7 +393,7 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
<button
type="button"
className="link-button link-button-sm m-1 "
onClick={handleShow}
onClick={()=>setshowModalBuilding(true)}
>
<i className="bx bx-plus-circle me-2"></i>
Manage Building
@ -449,7 +401,7 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
<button
type="button"
className="link-button m-1"
onClick={() => openFloorModel()}
onClick={()=>setshowModalFloor(true)}
>
<i className="bx bx-plus-circle me-2"></i>
Manage Floors
@ -473,15 +425,16 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
</div>
</div>
<div className="row ">
{loading && <p>Loading....</p>}
{project && project.buildings?.length > 0 && (
{isLoading && <p>Loading....</p>}
{projectInfra && projectInfra?.length > 0 && (
<InfraTable
buildings={project?.buildings}
projectId={project.id}
buildings={projectInfra}
projectId={projectId}
handleFloor={submitData}
signalRHandler = {signalRHandler}
signalRHandler ={signalRHandler}
/>
)}
{!isLoading && projectInfra?.length == 0 && <div className="mt-5"><p>No Infra Avaiable</p></div>}
</div>
</div>
</div>

View File

@ -23,7 +23,6 @@ const ProjectModal = ({modalConfig,closeModal}) => {
></button>
<div className="text-center mb-2"></div>
{/* Modal Component */}
{modalConfig?.type === "assignRole" && <AssignRole assignData={modalConfig?.data} onClose={closeModal} />}

View File

@ -17,7 +17,7 @@ const ProjectNav = ({ onPillClick, activePill }) => {
className={`nav-link ${activePill === "profile" ? "active" : ""}`}
href="#"
onClick={(e) => {
e.preventDefault(); // Prevent page reload
e.preventDefault();
onPillClick("profile");
}}
>
@ -29,7 +29,7 @@ const ProjectNav = ({ onPillClick, activePill }) => {
className={`nav-link ${activePill === "teams" ? "active" : ""}`}
href="#"
onClick={(e) => {
e.preventDefault(); // Prevent page reload
e.preventDefault();
onPillClick("teams");
}}
>
@ -41,27 +41,14 @@ const ProjectNav = ({ onPillClick, activePill }) => {
className={`nav-link ${activePill === "infra" ? "active" : ""}`}
href="#"
onClick={(e) => {
e.preventDefault(); // Prevent page reload
e.preventDefault();
onPillClick("infra");
}}
>
<i className="bx bx-grid-alt bx-sm me-1_5"></i> <span className="d-none d-md-inline">Infrastructure</span>
</a>
</li>
{/* <li className="nav-item">
<a
className={`nav-link ${
activePill === "workplan" ? "active" : ""
}`}
href="#"
onClick={(e) => {
e.preventDefault(); // Prevent page reload
onPillClick("workplan");
}}
>
<i className="bx bx-link bx-sm me-1_5"></i> Work Plan
</a>
</li> */}
<li className="nav-item">
<a
className={`nav-link ${

View File

@ -1,9 +1,9 @@
import React from "react";
import {useEmployeesByProjectAllocated, useProjects} from "../../hooks/useProjects";
const ProjectOverview = ({project}) =>
{
const {projects} = useProjects()
const ProjectOverview = ({project}) =>{
const {projects} = useProjects()
const project_detail = projects.find( ( pro ) => pro.id == project )
return (
<div className="card mb-6">

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect, useCallback } from "react";
import MapUsers from "./MapUsers";
import { Link, NavLink, useNavigate } from "react-router-dom";
import { Link, NavLink, useNavigate, useParams } from "react-router-dom";
import showToast from "../../services/toastService";
import Avatar from "../common/Avatar";
@ -16,7 +16,9 @@ import ConfirmModal from "../common/ConfirmModal";
import eventBus from "../../services/eventBus";
import {useEmployeesByProjectAllocated, useManageProjectAllocation} from "../../hooks/useProjects";
const Teams = ({ project }) => {
const Teams = () =>
{
const {projectId} = useParams()
const dispatch = useDispatch();
const { data, loading } = useMaster();
@ -35,7 +37,7 @@ const Teams = ({ project }) => {
const HasAssignUserPermission = useHasUserPermission( ASSIGN_TO_PROJECT );
const [ IsDeleteModal, setIsDeleteModal ] = useState( false )
const {projectEmployees, loading:employeeLodaing, refetch} = useEmployeesByProjectAllocated( project.id )
const {projectEmployees, loading:employeeLodaing, refetch} = useEmployeesByProjectAllocated( projectId )
const {
mutate: submitAllocations,
isPending,
@ -216,11 +218,11 @@ const {
const handler = useCallback(
(msg) => {
if (msg.projectIds.some((item) => item === project.id)) {
if (msg.projectIds.some((item) => item === projectId)) {
refetch();
}
},
[project.id, refetch]
[projectId, refetch]
);
useEffect(() => {
@ -251,7 +253,7 @@ const {
aria-hidden="true"
>
<MapUsers
projectId={project?.id}
projectId={projectId}
onClose={onModelClose}
empJobRoles={empJobRoles}
onSubmit={handleEmpAlicationFormSubmit}

View File

@ -236,7 +236,11 @@ export const useEmployeesByProjectAllocated = (selectedProject) =>
const res = await ProjectRepository.getProjectAllocation( selectedProject );
return res.data || res
},
enabled:!!selectedProject
enabled: !!selectedProject,
onError: ( error ) =>
{
showToast(error.message || "Error while Fetching project Allocated Employees", "error");
}
} )
return {
@ -256,7 +260,11 @@ export const useProjectDetails = ( projectId,isAuto = true ) =>
const res = await ProjectRepository.getProjectByprojectId( projectId );
return res.data || res;
},
enabled:!!projectId && isAuto
enabled: !!projectId && isAuto,
onError: ( error ) =>
{
showToast(error.message || "Error while Fetching project Details", "error");
}
} )
return { projects_Details, loading:isLoading, error, refetch };
}
@ -270,7 +278,11 @@ export const useProjectsByEmployee = (employeeId) =>
const res = await ProjectRepository.getProjectsByEmployee( employeeId );
return res.data || res;
},
enabled: !!employeeId
enabled: !!employeeId,
onError: ( error ) =>
{
showToast(error.message || "Error while Fetching project Employee", "error");
}
})
return {projectList, loading:isLoading,error,refetch }
}
@ -284,12 +296,56 @@ export const useProjectName = () =>
const res = await ProjectRepository.projectNameList();
return res.data || res;
},
onError: ( error ) =>
{
showToast(error.message || "Error while Fetching project Name", "error");
}
} )
return {projectNames:data,loading:isLoading,Error:error,refetch}
}
export const useProjectInfra = (projectId) => {
const {
data: projectInfra,
isLoading,
error,
} = useQuery({
queryKey: ["ProjectInfra", projectId],
queryFn: async () => {
const res = await ProjectRepository.getProjectInfraByproject(projectId);
return res.data;
},
enabled: !!projectId ,
onError: (error) => {
showToast(error.message || "Error while fetching project infra", "error");
},
});
return { projectInfra, isLoading, error };
};
export const useProjectTasks = (workAreaId,IsExpandedArea=false) =>
{
const { data:ProjectTaskList,isLoading,error } = useQuery( {
queryKey: [ "WorkItems",workAreaId ],
queryFn: async () =>
{
const res = await ProjectRepository.getProjectTasksByWorkArea(workAreaId);
return res.data;
},
enabled: !!workAreaId && !!IsExpandedArea,
onError: ( error ) =>
{
showToast(error.message || "Error while Fetching project Tasks", "error");
}
} )
return {ProjectTaskList,isLoading,error}
}
// -- Mutation-------------------------------
@ -371,21 +427,22 @@ export const useUpdateProject = ({ onSuccessCallback }) => {
};
export const useManageProjectInfra = () => {
export const useManageProjectInfra = ( {onSuccessCallback} ) =>
{
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({infraObject,projectId}) => {
mutationFn: async ( {infraObject, projectId} ) =>
{
return await ProjectRepository.manageProjectInfra(infraObject);
},
onSuccess: ( response, variables ) =>
onSuccess: ( data, variables ) =>
{
const { projectId } = variables;
showToast( "Details updated successfully.", "success" );
queryClient.invalidateQueries(["projectinfo", projectId]);
queryClient.invalidateQueries(["ProjectInfra", projectId]);
if (onSuccessCallback) onSuccessCallback(data,variables);
},
onError: (error) => {
showToast(error.message || "Failed to update task details", "error");
showToast(error.message || "Failed to update Project Infra", "error");
},
});
};
@ -433,3 +490,27 @@ export const useManageProjectAllocation = ({
isError,
};
};
export const useManageTask = ({onSuccessCallback}) =>
{
const queryClient = useQueryClient();
return useMutation( {
mutationFn: async ( payload ) => await ProjectRepository.manageProjectTasks( payload ),
onSuccess: ( data, variables ) =>
{
queryClient.invalidateQueries(["WorkItems"])
showToast( 'Activity Updated Successfully', 'success' );
if (onSuccessCallback) onSuccessCallback(data);
},
onError: (error) =>
{
const message =
error?.response?.data?.message || error.message || 'Error occurred during API call';
showToast(message, 'error');
}
})
}

View File

@ -78,14 +78,10 @@ const ProjectDetails = () => {
return (
<div className="row">
<div className="col-xl-4 col-lg-5 col-md-5 mt-5">
{/* About User */}
<AboutProject data={projects_Details}></AboutProject>
{/* About User */}
<AboutProject ></AboutProject>
</div>
<div className="col-xl-4 col-lg-5 col-md-5 mt-5">
{/* Profile Overview */}
<ProjectOverview project={projectId} />
{/* Profile Overview */}
</div>
</div>
);
@ -95,7 +91,7 @@ const ProjectDetails = () => {
<div className="row">
<div className="col-lg-12 col-xl-12">
{/* Teams */}
<Teams project={projects_Details}></Teams>
<Teams ></Teams>
{/* Teams */}
</div>
</div>
@ -135,26 +131,12 @@ const ProjectDetails = () => {
useEffect(() => {
dispatch(setProjectId(projectId));
// setProject(projects_Details);
// setProjectDetails(projects_Details);
}, [projects_Details, projectId]);
const handler = useCallback(
(msg) => {
if (msg.keyword === "Update_Project" && project.id === msg.response.id) {
// clearCacheKey("projectInfo")
// ProjectRepository.getProjectByprojectId(projectId)
// .then((response) => {
// setProjectDetails(response.data);
// setProject(response.data);
// cacheData("projectInfo", { projectId, data: response.data });
// setLoading(false);
// })
// .catch((error) => {
// console.error(error);
// setError("Failed to fetch data.");
// setLoading(false);
// });
refetch()
}
},

View File

@ -23,7 +23,11 @@ const ProjectRepository = {
deleteProject: ( id ) => api.delete( `/projects/${ id }` ),
getProjectsByEmployee: ( id ) => api.get( `/api/project/assigned-projects/${ id }` ),
updateProjectsByEmployee:(id,data)=>api.post(`/api/project/assign-projects/${id}`,data),
projectNameList:()=>api.get("/api/project/list/basic")
projectNameList: () => api.get( "/api/project/list/basic" ),
getProjectDetails:(id)=>api.get(`/api/project/details/${id}`),
getProjectInfraByproject: ( id ) => api.get( `/api/project/infra-details/${ id }` ),
getProjectTasksByWorkArea:(id)=>api.get(`/api/project/tasks/${id}`)
};
export const TasksRepository = {

View File

@ -11,9 +11,9 @@ export const VIEW_PROJECTS = "6ea44136-987e-44ba-9e5d-1cf8f5837ebc"
export const MANAGE_EMPLOYEES = "a97d366a-c2bb-448d-be93-402bd2324566"
export const MANAGE_PROJECT_INFRA = "f2aee20a-b754-4537-8166-f9507b44585b"
export const MANAGE_PROJECT_INFRA = "cf2825ad-453b-46aa-91d9-27c124d63373"
export const VIEW_PROJECT_INFRA = "c7b68e33-72f0-474f-bd96-77636427ecc8"
export const VIEW_PROJECT_INFRA = "8d7cc6e3-9147-41f7-aaa7-fa507e450bd4"
export const REGULARIZE_ATTENDANCE ="57802c4a-00aa-4a1f-a048-fd2f70dd44b6"