Compare commits

..

61 Commits

Author SHA1 Message Date
58fe364821 Merge pull request 'React Query Integration for Server State Sync in Clinet' (#245) from react-query into main
Reviewed-on: #245
2025-07-11 11:32:18 +00:00
Pramod Mahajan
5f54f5e29a added project status label that was missing, during changed structure of project details 2025-07-10 18:12:45 +05:30
Pramod Mahajan
c018ffa455 if assigned employee to project, that employees should display instantly inside Assigned task view and employee list 2025-07-10 13:18:04 +05:30
Pramod Mahajan
fe4426af9e fixed project updation in project about 2025-07-09 16:59:36 +05:30
Pramod Mahajan
01591c429e added generic modal for edit profile 2025-07-09 16:37:09 +05:30
Pramod Mahajan
bba4315a8e clear button class changed primary to secondary in manage employee form 2025-07-09 16:28:14 +05:30
Pramod Mahajan
2d33eede45 changed class to clasName 2025-07-09 16:26:40 +05:30
Pramod Mahajan
710af87db1 removed custome hook for loading state, becuase react-hook provide own state 2025-07-09 16:04:39 +05:30
Pramod Mahajan
fca3f03a44 fixed searching for employee 2025-07-09 14:33:21 +05:30
Pramod Mahajan
d818ea9265 vissible data after updated inside projecr details page 2025-07-09 11:48:42 +05:30
Pramod Mahajan
87edce4c3d added signalr events 2025-07-09 10:42:04 +05:30
Pramod Mahajan
cb7df469c2 added aspect ratio for image view in task comment 2025-07-08 15:24:18 +05:30
Pramod Mahajan
3968a47d98 fixed : project updation issue 2025-07-08 15:11:03 +05:30
Pramod Mahajan
ae07e9629b reset default correct activity for edit Task 2025-07-08 13:33:43 +05:30
Pramod Mahajan
16c7e971b1 updateed payload for createdTask fun , for getting workAreaId 2025-07-08 13:32:37 +05:30
Pramod Mahajan
f7f9c4044b cache invalidation for calling fro workItem after assign task for today planne 2025-07-08 13:31:21 +05:30
Pramod Mahajan
2c4ad2ba4a optimized to prevented for maximum deep rendering 2025-07-08 13:30:11 +05:30
Pramod Mahajan
ce37d5f447 added date util fun for UTC to Local zone 2025-07-08 13:29:09 +05:30
Pramod Mahajan
a146d4d15e Merge branch 'main' of https://git.marcoaiot.com/admin/marco.pms.web into react-query 2025-07-08 12:28:52 +05:30
Pramod Mahajan
5192ce2fc6 rename class to className 2025-07-07 17:02:02 +05:30
Pramod Mahajan
f24b66ae1c set default activity initially 2025-07-07 17:01:02 +05:30
Pramod Mahajan
58187db80a added default active status at create new project 2025-07-07 11:09:18 +05:30
Pramod Mahajan
9447178985 added format number 2025-07-07 10:59:51 +05:30
Pramod Mahajan
035ef6c099 Merge branch 'main' of https://git.marcoaiot.com/admin/marco.pms.web into react-query 2025-07-06 22:18:54 +05:30
Pramod Mahajan
10ee3796d3 Optimized to avoid unnecessary API calls 2025-07-06 22:10:13 +05:30
Pramod Mahajan
8676ff24a5 handle to prevent unwanted api calling 2025-07-06 15:51:46 +05:30
Pramod Mahajan
b5b1f9d31f added completedWork , plannedWork for progrss line with formatnumber util 2025-07-06 13:11:15 +05:30
Pramod Mahajan
485c7f2347 format number 2025-07-06 12:39:39 +05:30
Pramod Mahajan
28c226904e closed modal after update activity and format number of plannedwork and completed work at project overview 2025-07-04 12:39:10 +05:30
Pramod Mahajan
eb1c4fc765 show denied msg on Daily Task planning page if don't have manage infra and assign task 2025-07-04 12:37:55 +05:30
Pramod Mahajan
afac84dc9b now displayed cancel and submit button at initially - bug643 2025-07-04 11:47:02 +05:30
Pramod Mahajan
20c9dca759 added formatNumber at globally for reusable 2025-07-04 11:29:24 +05:30
Pramod Mahajan
5949e7c277 improved empty state UI when no projects are found 2025-07-04 11:10:22 +05:30
Pramod Mahajan
a107041153 chnanged status color, becuase Active status is hold success color, therefor chnages success to primary for In-progress status 2025-07-04 10:53:53 +05:30
Pramod Mahajan
ca476d6f6e removed unused code 2025-07-04 10:52:07 +05:30
Pramod Mahajan
2e54ed1dd9 display project Name at header when location have project details 2025-07-04 10:51:41 +05:30
Pramod Mahajan
4c6e0b5c9e added project status - 'In-Progress' 2025-07-04 10:50:57 +05:30
Pramod Mahajan
9961b4aba7 removed unused code 2025-07-04 10:49:35 +05:30
Pramod Mahajan
b39a3d7e97 fixed: visible sub task without refresh page 2025-07-03 19:10:49 +05:30
Pramod Mahajan
094811a8ec floor Name field clear clear after complete edit or create operation 2025-07-03 17:09:31 +05:30
Pramod Mahajan
e6d6f0ac30 Handle undefined 'variables' in employee assignment toast logic 2025-07-03 13:16:01 +05:30
Pramod Mahajan
e24b873677 added projectId , project id was missed 2025-07-03 12:16:35 +05:30
Pramod Mahajan
25b2f6973c fixed format planned vs remaining work values to avoid float precision issues in task UI 2025-07-02 20:07:35 +05:30
Pramod Mahajan
80083aa5db updated some queries and mutation for prevent to duplicates calls 2025-07-02 19:42:43 +05:30
Pramod Mahajan
52c5e3d2d2 integrated reactquery for project infra 2025-07-02 13:20:17 +05:30
Pramod Mahajan
2f110b7ead Merge branch 'main' of https://git.marcoaiot.com/admin/marco.pms.web into react-query 2025-07-02 09:49:20 +05:30
Pramod Mahajan
0c8ff7b28c partially integrated react-query in project infra 2025-07-02 01:57:02 +05:30
Pramod Mahajan
a02a33a247 partially implementation of masters in react query hooks 2025-07-01 13:23:16 +05:30
Pramod Mahajan
8dbce6dd49 Fixed issue where query remained disabled and cache was undefined 2025-06-30 21:36:54 +05:30
Pramod Mahajan
f848f97d87 implement partial react-query in task assignment flow 2025-06-30 20:20:52 +05:30
Pramod Mahajan
7fbbe98373 fixed syntax mistak 2025-06-30 16:20:42 +05:30
Pramod Mahajan
467d5d4b13 integrate React Query for project details and real-time updates 2025-06-29 00:55:02 +05:30
Pramod Mahajan
22e65c167e prevent unnwanted api calling 2025-06-28 10:38:01 +05:30
Pramod Mahajan
cfa1c6366d added props type of emp for call api purpose 2025-06-27 15:56:23 +05:30
Pramod Mahajan
c86065f224 added queryClient obj inside authlayout for reuse 2025-06-27 15:54:36 +05:30
Pramod Mahajan
7afea8e52a added type of employees, for calling api 2025-06-27 15:53:20 +05:30
Pramod Mahajan
3802be1673 seprated moda querClient obj inside AuthLayout ,because we can reuse queryClient 2025-06-27 15:52:29 +05:30
Pramod Mahajan
c0aa178af7 fixed vertical layout 2025-06-27 10:20:32 +05:30
Pramod Mahajan
fa32913e34 Replaced normal hooks to react-query Hook - Employee 2025-06-26 12:28:02 +05:30
Pramod Mahajan
1b5fa173f0 return one more params 2025-06-26 12:26:51 +05:30
Pramod Mahajan
664fe2473f added optional chain 2025-06-26 12:21:19 +05:30
66 changed files with 4980 additions and 5372 deletions

55
package-lock.json generated
View File

@ -11,6 +11,8 @@
"@hookform/resolvers": "^3.10.0", "@hookform/resolvers": "^3.10.0",
"@microsoft/signalr": "^8.0.7", "@microsoft/signalr": "^8.0.7",
"@reduxjs/toolkit": "^2.5.0", "@reduxjs/toolkit": "^2.5.0",
"@tanstack/react-query": "^5.81.2",
"@tanstack/react-query-devtools": "^5.81.2",
"@types/web": "^0.0.216", "@types/web": "^0.0.216",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"apexcharts": "^4.5.0", "apexcharts": "^4.5.0",
@ -1423,6 +1425,59 @@
"@swc/counter": "^0.1.3" "@swc/counter": "^0.1.3"
} }
}, },
"node_modules/@tanstack/query-core": {
"version": "5.81.2",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.81.2.tgz",
"integrity": "sha512-QLYkPdrudoMATDFa3MiLEwRhNnAlzHWDf0LKaXUqJd0/+QxN8uTPi7bahRlxoAyH0UbLMBdeDbYzWALj7THOtw==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/query-devtools": {
"version": "5.81.2",
"resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.81.2.tgz",
"integrity": "sha512-jCeJcDCwKfoyyBXjXe9+Lo8aTkavygHHsUHAlxQKKaDeyT0qyQNLKl7+UyqYH2dDF6UN/14873IPBHchcsU+Zg==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/react-query": {
"version": "5.81.2",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.81.2.tgz",
"integrity": "sha512-pe8kFlTrL2zFLlcAj2kZk9UaYYHDk9/1hg9EBaoO3cxDhOZf1FRGJeziSXKrVZyxIfs7b3aoOj/bw7Lie0mDUg==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "5.81.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^18 || ^19"
}
},
"node_modules/@tanstack/react-query-devtools": {
"version": "5.81.2",
"resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.81.2.tgz",
"integrity": "sha512-TX0OQ4cbgX6z2uN8c9x0QUNbyePGyUGdcgrGnV6TYEJc7KPT8PqeASuzoA5NGw1CiMGvyFAkIGA2KipvhM9d1g==",
"license": "MIT",
"dependencies": {
"@tanstack/query-devtools": "5.81.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"@tanstack/react-query": "^5.81.2",
"react": "^18 || ^19"
}
},
"node_modules/@types/babel__core": { "node_modules/@types/babel__core": {
"version": "7.20.5", "version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",

View File

@ -14,6 +14,8 @@
"@hookform/resolvers": "^3.10.0", "@hookform/resolvers": "^3.10.0",
"@microsoft/signalr": "^8.0.7", "@microsoft/signalr": "^8.0.7",
"@reduxjs/toolkit": "^2.5.0", "@reduxjs/toolkit": "^2.5.0",
"@tanstack/react-query": "^5.81.2",
"@tanstack/react-query-devtools": "^5.81.2",
"@types/web": "^0.0.216", "@types/web": "^0.0.216",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"apexcharts": "^4.5.0", "apexcharts": "^4.5.0",

View File

@ -1,21 +1,25 @@
import { DireProvider } from "./Context/DireContext"; import { DireProvider } from "./Context/DireContext";
import AppRoutes from "./router/AppRoutes"; import AppRoutes from "./router/AppRoutes";
import { ToastContainer } from "react-toastify"; import { ToastContainer } from "react-toastify";
import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { queryClient } from "./layouts/AuthLayout";
const App = () => { const App = () => {
return ( return (
<div className="app"> <div className="app">
<DireProvider> <QueryClientProvider client={queryClient}>
<AppRoutes /> <DireProvider>
</DireProvider> <AppRoutes />
</DireProvider>
<ToastContainer>
</ToastContainer>
<ToastContainer />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</div> </div>
); );
}; };
export default App export default App;

View File

@ -8,7 +8,7 @@ import WorkAreaModel from "../Project/Infrastructure/WorkAreaModel";
import TaskModel from "../Project/Infrastructure/TaskModel"; import TaskModel from "../Project/Infrastructure/TaskModel";
import ProjectRepository from "../../repositories/ProjectRepository"; import ProjectRepository from "../../repositories/ProjectRepository";
import Breadcrumb from "../../components/common/Breadcrumb"; 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 {useHasUserPermission} from "../../hooks/useHasUserPermission";
import {MANAGE_PROJECT_INFRA} from "../../utils/constants"; import {MANAGE_PROJECT_INFRA} from "../../utils/constants";
import {useDispatch, useSelector} from "react-redux"; import {useDispatch, useSelector} from "react-redux";
@ -21,17 +21,14 @@ const InfraPlanning = () =>
{ {
const {profile: LoggedUser, refetch : fetchData} = useProfile() const {profile: LoggedUser, refetch : fetchData} = useProfile()
const dispatch = useDispatch() const dispatch = useDispatch()
const {projects,loading:project_listLoader,error:projects_error} = useProjects()
const selectedProject = useSelector((store)=>store.localVariables.projectId) const selectedProject = useSelector((store)=>store.localVariables.projectId)
const {projectInfra, isLoading, error} = useProjectInfra( selectedProject )
const ManageInfra = useHasUserPermission( MANAGE_PROJECT_INFRA ) 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 ) const reloadedData = useSelector( ( store ) => store.localVariables.reload )
// useEffect( () =>
// {
// dispatch(setProjectId(projects[0]?.id))
// }, [ projects ] )
useEffect( () => useEffect( () =>
{ {
@ -47,34 +44,20 @@ const InfraPlanning = () =>
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4"> <div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
<div className="card"> <div className="card">
<div className="card-body" style={{ padding: "0.5rem" }}> <div className="card-body" style={{ padding: "0.5rem" }}>
<div className="align-items-center"> {ManageInfra ? (
{/* <div className="row "> <div className="align-items-center">
<div className="col-sm-3 col-8 text-start mb-1">
<select name="DataTables_Table_0_length"
aria-controls="DataTables_Table_0"
className="form-select form-select-sm"
value={selectedProject}
onChange={(e)=>dispatch(setProjectId(e.target.value))}
aria-label=""
>
{(project_listLoader || projects.length < 0) && <option value="Loading..." disabled>Loading...</option> }
{!project_listLoader && projects?.map((project)=>(
<option key={project.id} value={project.id}>{project.name}</option>
))}
</select>
</div>
</div> */}
<div className="row "> <div className="row ">
{project_deatilsLoader && ( <p>Loading...</p> )} {isLoading && ( <p>Loading...</p> )}
{( !project_deatilsLoader && projects_Details?.buildings.length === 0 ) && ( <p>No Result Found</p> )} {( !isLoading && projectInfra?.length === 0 ) && ( <p>No Result Found</p> )}
{(!isLoading && projectInfra?.length > 0) && (<InfraTable buildings={projectInfra} projectId={selectedProject}/>)}
{(!project_deatilsLoader && projects_Details?.buildings?.length > 0) && (<InfraTable buildings={projects_Details?.buildings} projectId={projects_Details.id}/>)}
</div> </div>
</div> </div>
) : (
<div className="text-center">
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
<p>Access Denied: You don't have permission to perform this action. !</p>
</div>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,21 +1,29 @@
import React, { useState,useEffect } from "react"; import React, { useState, useEffect } from "react";
import { formatDate } from "../../utils/dateUtils"; import { formatDate } from "../../utils/dateUtils";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import { TasksRepository } from "../../repositories/TaskRepository"; import { TasksRepository } from "../../repositories/TaskRepository";
import { useReportTask } from "../../hooks/useTasks";
export const ReportTask = ({ report, closeModal, refetch }) => { export const ReportTask = ({ report, closeModal }) => {
const [loading, setloading] = useState(false); const { mutate: reportTask, isPending } = useReportTask({
onSuccessCallback: () => {
reset();
closeModal();
},
});
const maxPending = const maxPending =
report?.workItem?.plannedWork - report?.workItem?.completedWork; report?.workItem?.plannedWork - report?.workItem?.completedWork;
const schema = z.object({ const schema = z.object({
completedTask: z completedTask: z.preprocess(
.preprocess( (val) =>
(val) => (val === "" || val === null || Number.isNaN(val) ? undefined : Number(val)), val === "" || val === null || Number.isNaN(val)
? undefined
: Number(val),
z z
.number({ .number({
required_error: "Completed Work must be a number", required_error: "Completed Work must be a number",
@ -26,49 +34,35 @@ export const ReportTask = ({ report, closeModal, refetch }) => {
message: `Completed task cannot exceed total pending tasks: ${maxPending}`, message: `Completed task cannot exceed total pending tasks: ${maxPending}`,
}) })
), ),
comment: z.string().min(1, "Comment cannot be empty"), comment: z.string().min(1, "Comment cannot be empty"),
}); });
const { const {
register, register,
handleSubmit, handleSubmit,
formState: {errors}, formState: { errors },
reset reset,
} = useForm({ } = useForm({
resolver: zodResolver(schema), resolver: zodResolver(schema),
defaultValues: { completedTask: 0, comment: "" }, defaultValues: { completedTask: 0, comment: "" },
}); });
useEffect(() => {
useEffect(() => { if (report) {
if (report) { reset({ completedTask: 0, comment: "" });
reset({ completedTask: 0, comment: "" }); // optional: customize default if needed
}
}, [report, reset]);
const onSubmit = async (data) => {
try {
setloading(true);
const reportData = {
...data,
id: report?.id,
reportedDate: new Date().toISOString(),
checkList: [],
};
let response = await TasksRepository.reportTask(reportData);
showToast("Task Reported Successfully.", "success");
refetch();
reset()
setloading(false);
closeModal();
} catch ( error )
{
const msg = error.response.data.message || error.message || "Error Occur During Api Call"
showToast(msg, "error");
} }
}, [report, reset]);
const onSubmit = (data) => {
const reportData = {
...data,
id: report?.id,
reportedDate: new Date().toISOString(),
checkList: [],
};
reportTask({ reportData, workAreaId: report?.workItem?.workArea?.id });
}; };
const handleClose = () => { const handleClose = () => {
closeModal(); closeModal();
@ -76,153 +70,130 @@ useEffect(() => {
}; };
return ( return (
<div className="container m-0"> <div className="container m-0">
<div className="text-center"> <div className="text-center">
<p className="fs-6 fw-semibold">Report Task</p> <p className="fs-6 fw-semibold">Report Task</p>
</div> </div>
<div className="mb-1 row text-start"> <div className="mb-1 row text-start">
<label <label htmlFor="html5-text-input" className="col-md-4 col-form-label">
htmlFor="html5-text-input" Assigned Date :{" "}
className="col-md-4 col-form-label" </label>
> <div className="col-md-8 text-start">
Assigned Date :{" "} <label className="col-md-2 col-form-label">
</label> {formatDate(report?.assignmentDate)}
<div className="col-md-8 text-start"> </label>
<label className="col-md-2 col-form-label"> </div>
{formatDate(report?.assignmentDate)} </div>
</label> <div className="mb-1 row text-start">
</div> <label htmlFor="html5-search-input" className="col-md-4 col-form-label">
</div> Assigned By :{" "}
<div className="mb-1 row text-start"> </label>
<label <div className="col-md-8 text-start">
htmlFor="html5-search-input" <label className=" col-form-label">{` ${report?.assignedBy.firstName} ${report?.assignedBy.lastName}`}</label>
className="col-md-4 col-form-label" </div>
> </div>
Assigned By :{" "} <div className="mb-1 row text-start">
</label> <label htmlFor="html5-email-input" className="col-md-4 col-form-label">
<div className="col-md-8 text-start"> Wrok Area :
<label className=" col-form-label">{` ${report?.assignedBy.firstName} ${report?.assignedBy.lastName}`}</label> </label>
</div> <div className="col-md-8 text-start text-wrap">
</div> <label className=" col-form-label">
<div className="mb-1 row text-start"> {" "}
<label {report?.workItem?.workArea?.floor?.building?.name}{" "}
htmlFor="html5-email-input" <i className="bx bx-chevron-right"></i>{" "}
className="col-md-4 col-form-label" {report?.workItem?.workArea?.floor?.floorName}{" "}
> <i className="bx bx-chevron-right"> </i>
Wrok Area : {report?.workItem?.workArea?.areaName}
</label> </label>
<div className="col-md-8 text-start text-wrap"> </div>
<label className=" col-form-label"> </div>
{" "} <div className="mb-1 row text-start">
{report?.workItem?.workArea?.floor?.building?.name}{" "} <label htmlFor="html5-email-input" className="col-md-4 col-form-label">
<i className="bx bx-chevron-right"></i>{" "} Activity :
{report?.workItem?.workArea?.floor?.floorName}{" "} </label>
<i className="bx bx-chevron-right"> </i> <div className="col-md-8 text-start text-wrap">
{report?.workItem?.workArea?.areaName} <label className=" col-form-label">
</label> {report?.workItem?.activityMaster.activityName}
</div> </label>
</div> </div>
<div className="mb-1 row text-start"> </div>
<label <div className="mb-1 row text-start">
htmlFor="html5-email-input" <label htmlFor="html5-email-input" className="col-md-4 col-form-label">
className="col-md-4 col-form-label" Team Size :
> </label>
Activity : <div className="col-md-8 text-start text-wrap">
</label> <label className=" col-form-label">
<div className="col-md-8 text-start text-wrap"> {report?.teamMembers?.length}
<label className=" col-form-label"> </label>
{report?.workItem?.activityMaster.activityName} </div>
</label> </div>
</div> <div className="mb-1 row text-start">
</div> <label htmlFor="html5-email-input" className="col-md-4 col-form-label">
<div className="mb-1 row text-start"> Assigned :
<label </label>
htmlFor="html5-email-input" <div className="col-md-8 text-start text-wrap">
className="col-md-4 col-form-label" <label className=" col-form-label">
> {report?.plannedTask} of{" "}
Team Size : {report?.workItem.plannedWork - report?.workItem.completedWork}{" "}
</label> Pending
<div className="col-md-8 text-start text-wrap"> </label>
<label className=" col-form-label"> </div>
{report?.teamMembers?.length} </div>
</label> <form onSubmit={handleSubmit(onSubmit)}>
</div> <div className="mb-1 row text-start">
</div> <label
<div className="mb-1 row text-start"> htmlFor="html5-email-input"
<label className="col-md-4 col-form-label"
htmlFor="html5-email-input" >
className="col-md-4 col-form-label" Completed Work
> </label>
Assigned : <div className="col-md-8 text-start text-wrap">
</label> <input
<div className="col-md-8 text-start text-wrap"> {...register("completedTask", { valueAsNumber: true })}
<label className=" col-form-label"> id="smallInput"
{report?.plannedTask} of{" "} className="form-control form-control-sm"
{report?.workItem.plannedWork - type="number"
report?.workItem.completedWork}{" "} placeholder="Completed Work"
Pending />
</label> {errors.completedTask && (
</div> <div className="danger-text">{errors.completedTask.message}</div>
</div> )}
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mb-1 row text-start">
<label
htmlFor="html5-email-input"
className="col-md-4 col-form-label"
>
Completed Work
</label>
<div className="col-md-8 text-start text-wrap">
<input
{...register("completedTask", { valueAsNumber: true })}
id="smallInput"
className="form-control form-control-sm"
type="number"
placeholder="Completed Work"
/>
{errors.completedTask && (
<div className="danger-text">
{errors.completedTask.message}
</div>
)}
</div>
</div>
<div className="mb-1 row text-start">
<label
htmlFor="html5-email-input"
className="col-md-4 col-form-label"
>
Comment
</label>
<div className="col-md-8 text-start text-wrap">
<textarea
{...register("comment")}
className="form-control"
id="exampleFormControlTextarea1"
rows="1"
placeholder="Enter comment"
/>
{errors.comment && (
<div className="danger-text">{errors.comment.message}</div>
)}
</div>
</div>
<div className="col-12 text-center my-2">
<button type="submit" className="btn btn-sm btn-primary me-3">
{loading ? "Please wait" : "Submit Report"}
</button>
<button
type="button"
className="btn btn-sm btn-label-secondary"
onClick={handleClose}
>
Cancel
</button>
</div>
</form>
</div> </div>
</div>
<div className="mb-1 row text-start">
<label
htmlFor="html5-email-input"
className="col-md-4 col-form-label"
>
Comment
</label>
<div className="col-md-8 text-start text-wrap">
<textarea
{...register("comment")}
className="form-control"
id="exampleFormControlTextarea1"
rows="1"
placeholder="Enter comment"
/>
{errors.comment && (
<div className="danger-text">{errors.comment.message}</div>
)}
</div>
</div>
<div className="col-12 text-center my-2">
<button type="submit" className="btn btn-sm btn-primary me-3" disabled={isPending}>
{isPending ? "Please wait" : "Submit Report"}
</button>
<button
type="button"
className="btn btn-sm btn-label-secondary"
onClick={handleClose}
disabled={isPending}
>
Cancel
</button>
</div>
</form>
</div>
); );
}; };

View File

@ -9,7 +9,7 @@ import Avatar from "../common/Avatar";
import { getBgClassFromHash } from "../../utils/projectStatus"; import { getBgClassFromHash } from "../../utils/projectStatus";
import { cacheData, getCachedData } from "../../slices/apiDataManager"; import { cacheData, getCachedData } from "../../slices/apiDataManager";
import ImagePreview from "../common/ImagePreview"; import ImagePreview from "../common/ImagePreview";
import { useAuditStatus } from "../../hooks/useTasks"; import { useAuditStatus, useSubmitTaskComment } from "../../hooks/useTasks";
const ReportTaskComments = ({ const ReportTaskComments = ({
commentsData, commentsData,
@ -48,7 +48,18 @@ const ReportTaskComments = ({
const [loading, setloading] = useState(false); const [loading, setloading] = useState(false);
const [comments, setComment] = useState([]); const [comments, setComment] = useState([]);
const { status, loading: auditStatusLoading } = useAuditStatus(); const { status, loading: auditStatusLoading } = useAuditStatus();
const [IsNeededSubTask, setIsNeededSubTask] = useState(false); const [ IsNeededSubTask, setIsNeededSubTask ] = useState( false );
const { submitComment, isPending } = useSubmitTaskComment({
actionAllow,
onSuccessCallback: (data) => {
setComment((prev) => [...prev, data]);
reset();
if ( actionAllow )
{
handleCloseAction(IsNeededSubTask);
}
},
});
const { const {
watch, watch,
@ -88,70 +99,16 @@ const ReportTaskComments = ({
} }
}, [comments]); }, [comments]);
const onSubmit = async (data) => { const onSubmit = (formData) => {
let payload = { submitComment({ data: formData, commentsData });
...data,
[actionAllow ? "id" : "taskAllocationId"]: commentsData?.id,
...(actionAllow ? {} : { commentDate: new Date().toISOString() }),
};
try {
setloading(true);
const resp = actionAllow
? await TasksRepository.auditTask(payload)
: await TasksRepository.taskComments(payload);
setComment((prevItems) => [...prevItems, resp.data]);
const taskList = getCachedData("taskList");
if (actionAllow) {
handleCloseAction(IsNeededSubTask);
showToast(
"Review submitted successfully. Record has been updated.",
"success"
);
} else {
if (taskList && taskList.data) {
const updatedTaskList = taskList.data.map((task) => {
if (task.id === resp.data.taskAllocationId) {
const existingComments = Array.isArray(task.comments)
? task.comments
: [];
return {
...task,
comments: [...existingComments, resp.data],
};
}
return task;
});
cacheData("taskList", {
data: updatedTaskList,
projectId: taskList.projectId,
});
}
showToast("Successfully Sent", "success");
}
reset();
setloading(false);
} catch (error) {
setloading(false);
showToast(
error.response.data?.message || "Something went wrong",
"error"
);
}
}; };
const selectedAuditStatus = watch("workStatus"); const selectedAuditStatus = watch("workStatus");
useEffect(() => { useEffect(() => {
reset({ reset({
approvedTask: defaultCompletedTask || 0, approvedTask: defaultCompletedTask || 0,
}); });
}, [ defaultCompletedTask ] ); }, [defaultCompletedTask]);
return ( return (
<div className="p-2 p-sm-1"> <div className="p-2 p-sm-1">
<div className="modal-body p-sm-4 p-0"> <div className="modal-body p-sm-4 p-0">
@ -233,7 +190,6 @@ const ReportTaskComments = ({
)} )}
</div> </div>
</div> </div>
<div className="d-flex align-items-center flex-wrap"> <div className="d-flex align-items-center flex-wrap">
<p className="fw-bold text-start m-0 me-1"> <p className="fw-bold text-start m-0 me-1">
@ -260,50 +216,50 @@ const ReportTaskComments = ({
<div className="fw-normal ms-2">{commentsData?.description}</div> <div className="fw-normal ms-2">{commentsData?.description}</div>
</div> </div>
{commentsData?.approvedBy && ( {commentsData?.approvedBy && (
<> <>
<hr className="my-1"/> <hr className="my-1" />
<div className="row"> <div className="row">
<div className="col-12 col-sm-6">
<div className="col-12 col-sm-6"> {commentsData.approvedBy && (
{commentsData.approvedBy && ( <div className="fw-bold text-start d-flex align-items-center">
<div className="fw-bold text-start d-flex align-items-center"> <i className="bx bx-user-check bx-lg me-1"></i>
<i className="bx bx-user-check bx-lg me-1"></i> <span className="me-2">Approved By:</span>
<span className="me-2">Approved By:</span> <div className="d-flex align-items-center fw-normal">
<div className="d-flex align-items-center fw-normal"> <Avatar
<Avatar firstName={commentsData.approvedBy.firstName}
firstName={commentsData.approvedBy.firstName} lastName={commentsData.approvedBy.lastName}
lastName={commentsData.approvedBy.lastName} size="xs"
size="xs" className="me-1"
className="me-1" />
/> {commentsData.approvedBy.firstName +
{commentsData.approvedBy.firstName + " " +
" " + commentsData.approvedBy.lastName}
commentsData.approvedBy.lastName} </div>
</div> </div>
)}
</div> </div>
)}
</div>
<div className="col-12 col-sm-6"> <div className="col-12 col-sm-6">
{commentsData?.workStatus != null && ( {commentsData?.workStatus != null && (
<div className="fw-bold my-2 text-start d-flex align-items-center"> <div className="fw-bold my-2 text-start d-flex align-items-center">
<i className="bx bx-time-five me-2"></i> <i className="bx bx-time-five me-2"></i>
<span className="me-2">Work Status :</span> <span className="me-2">Work Status :</span>
<span className="fw-normal"> <span className="fw-normal">
{commentsData?.workStatus.name} {commentsData?.workStatus.name}
{/* {commentsData?.} */} {/* {commentsData?.} */}
</span> </span>
</div>
)}
</div> </div>
)}
</div>
</div> </div>
<div className="col-12 d-flex"> <div className="col-12 d-flex">
<span className="fw-bold">Total Approved : </span><span className="ms-2">{commentsData?.completedTask }</span> <span className="fw-bold">Total Approved : </span>
</div> <span className="ms-2">{commentsData?.completedTask}</span>
</> )} </div>
</>
)}
{commentsData?.reportedPreSignedUrls?.length > 0 && ( {commentsData?.reportedPreSignedUrls?.length > 0 && (
<> <>
<p className="fw-bold m-0 text-start"> <p className="fw-bold m-0 text-start">
@ -319,54 +275,53 @@ const ReportTaskComments = ({
)} )}
<form onSubmit={handleSubmit(onSubmit)} className="text-start"> <form onSubmit={handleSubmit(onSubmit)} className="text-start">
{( actionAllow && !commentsData.approvedBy ) && ( {actionAllow && !commentsData.approvedBy && (
<> <>
<div className="row align-items-end my-1"> <div className="row align-items-end my-1">
<div className="col-6 col-sm-4 text-start"> <div className="col-6 col-sm-4 text-start">
<label>Completed</label> <label>Completed</label>
<input <input
className="form-control form-control-sm" className="form-control form-control-sm"
{...register("approvedTask")} {...register("approvedTask")}
type="number" type="number"
/> />
{errors.approvedTask && ( {errors.approvedTask && (
<p className="danger-text m-0"> <p className="danger-text m-0">
{errors.approvedTask.message} {errors.approvedTask.message}
</p> </p>
)}
</div>
<div className="col-6 col-sm-4 text-center align-items-end m-0">
<label htmlFor="workStatus" className="form-label">
Audit Status
</label>
<select
id="workStatus"
className={`form-select form-select-sm`}
{...register("workStatus")}
defaultValue=""
onChange={(e) => setValue("workStatus", e.target.value)}
>
<option value="" disabled>
Select Status
</option>
{auditStatusLoading ? (
<option disabled>Loading...</option>
) : (
status.map((stat) => (
<option key={stat.id} value={stat.id}>
{stat.name}
</option>
))
)} )}
</select> </div>
{errors.workStatus && ( <div className="col-6 col-sm-4 text-center align-items-end m-0">
<div className="danger-text"> <label htmlFor="workStatus" className="form-label">
{errors.workStatus.message} Audit Status
</div> </label>
)} <select
id="workStatus"
className={`form-select form-select-sm`}
{...register("workStatus")}
defaultValue=""
onChange={(e) => setValue("workStatus", e.target.value)}
>
<option value="" disabled>
Select Status
</option>
{auditStatusLoading ? (
<option disabled>Loading...</option>
) : (
status.map((stat) => (
<option key={stat.id} value={stat.id}>
{stat.name}
</option>
))
)}
</select>
{errors.workStatus && (
<div className="danger-text">
{errors.workStatus.message}
</div>
)}
</div>
</div> </div>
</div>
</> </>
)} )}
<i className="bx bx-comment-detail me-2"></i> <i className="bx bx-comment-detail me-2"></i>
@ -382,10 +337,16 @@ const ReportTaskComments = ({
)} )}
<div <div
className={` ${ className={` ${
(actionAllow && !commentsData.approvedBy)? " d-flex justify-content-between" : "text-end" actionAllow && !commentsData.approvedBy
? " d-flex justify-content-between"
: "text-end"
} mt-2`} } mt-2`}
> >
<div className={`form-check ${!(actionAllow && !commentsData.approvedBy) && "d-none"} `}> <div
className={`form-check ${
!(actionAllow && !commentsData.approvedBy) && "d-none"
} `}
>
<input <input
className="form-check-input" className="form-check-input"
type="checkbox" type="checkbox"
@ -403,15 +364,16 @@ const ReportTaskComments = ({
className="btn btn-sm btn-secondary" className="btn btn-sm btn-secondary"
onClick={closeModal} onClick={closeModal}
data-bs-dismiss="modal" data-bs-dismiss="modal"
disabled={isPending}
> >
Close Close
</button> </button>
<button <button
type="submit" type="submit"
className="btn btn-sm btn-primary ms-2" className="btn btn-sm btn-primary ms-2"
disabled={loading} disabled={isPending}
> >
{loading {isPending
? actionAllow ? actionAllow
? "Please Wait..." ? "Please Wait..."
: "Send..." : "Send..."

View File

@ -9,6 +9,7 @@ import {
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import ProjectRepository from "../../repositories/ProjectRepository"; import ProjectRepository from "../../repositories/ProjectRepository";
import { useTaskById } from "../../hooks/useTasks"; import { useTaskById } from "../../hooks/useTasks";
import {useManageTask} from "../../hooks/useProjects";
const subTaskSchema = z.object({ const subTaskSchema = z.object({
activityId: z.string().min(1, "Activity is required"), activityId: z.string().min(1, "Activity is required"),
@ -23,7 +24,6 @@ const SubTask = ({ activity, onClose }) => {
const [categoryData, setCategoryData] = useState([]); const [categoryData, setCategoryData] = useState([]);
const { activities, loading } = useActivitiesMaster(); const { activities, loading } = useActivitiesMaster();
const { categories, categoryLoading } = useWorkCategoriesMaster(); const { categories, categoryLoading } = useWorkCategoriesMaster();
const [isSubmitting, setIsSubmitting] = useState(false);
const { Task, loading: TaskLoading } = useTaskById(activity?.id); const { Task, loading: TaskLoading } = useTaskById(activity?.id);
const { const {
register, register,
@ -37,13 +37,19 @@ const SubTask = ({ activity, onClose }) => {
}); });
const selectedActivityId = watch("activityId"); const selectedActivityId = watch("activityId");
const selectedActivity = activities?.find((a) => a.id === selectedActivityId); const selectedActivity = activities?.find((a) => a.id === selectedActivityId);
const {mutate:createSubTask,isPending } = useManageTask( {
onSuccessCallback: () =>
{
showToast("Sub Task Created Successfully","success")
reset();
onClose();
}
} )
// Set categories when fetched
useEffect(() => { useEffect(() => {
setCategoryData(categories); setCategoryData(categories);
}, [categories]); }, [categories]);
// Set initial values from activity
useEffect(() => { useEffect(() => {
if (!TaskLoading && (Task?.workItem || activity)) { if (!TaskLoading && (Task?.workItem || activity)) {
reset({ reset({
@ -76,22 +82,8 @@ const SubTask = ({ activity, onClose }) => {
parentTaskId: activity?.id, parentTaskId: activity?.id,
comment: formData.comment, comment: formData.comment,
}; };
setIsSubmitting(true);
createSubTask([payload])
try {
await ProjectRepository.manageProjectTasks([payload]);
showToast("SubTask Created Successfully", "success");
setIsSubmitting(false);
reset();
onClose();
} catch (error) {
setIsSubmitting(false);
const msg =
error.response.message ||
error.message ||
"Error occured during API calling";
showToast(msg, "error");
}
}; };
return ( return (
<div className="container-xxl my-1"> <div className="container-xxl my-1">
@ -221,15 +213,15 @@ const SubTask = ({ activity, onClose }) => {
<button <button
type="submit" type="submit"
className="btn btn-sm btn-primary me-2" className="btn btn-sm btn-primary me-2"
disabled={isSubmitting} disabled={isPending}
> >
{isSubmitting ? "Please wait..." : "Submit"} {isPending ? "Please wait..." : "Submit"}
</button> </button>
<button <button
type="button" type="button"
className="btn btn-sm btn-secondary" className="btn btn-sm btn-secondary"
onClick={() => onClose()} onClick={() => onClose()}
disabled={isSubmitting} disabled={isPending}
> >
Cancel Cancel
</button> </button>

View File

@ -19,7 +19,7 @@ import { useProfile } from "../../hooks/useProfile";
const ManageBucket = () => { const ManageBucket = () => {
const { profile } = useProfile(); const { profile } = useProfile();
const [bucketList, setBucketList] = useState([]); const [bucketList, setBucketList] = useState([]);
const { employeesList } = useAllEmployees(false); const {employeesList} = useAllEmployees( false );
const [selectedEmployee, setSelectEmployee] = useState([]); const [selectedEmployee, setSelectEmployee] = useState([]);
const { buckets, loading, refetch } = useBuckets(); const { buckets, loading, refetch } = useBuckets();
const [action_bucket, setAction_bucket] = useState(false); const [action_bucket, setAction_bucket] = useState(false);

View File

@ -9,24 +9,26 @@ import { useDispatch } from "react-redux";
import { changeMaster } from "../../slices/localVariablesSlice"; import { changeMaster } from "../../slices/localVariablesSlice";
import { Link, useNavigate, useParams } from "react-router-dom"; import { Link, useNavigate, useParams } from "react-router-dom";
import { formatDate } from "../../utils/dateUtils"; import { formatDate } from "../../utils/dateUtils";
import { useEmployeeProfile } from "../../hooks/useEmployees"; import { useEmployeeProfile, useUpdateEmployee } from "../../hooks/useEmployees";
import { import {
cacheData, cacheData,
clearCacheKey, clearCacheKey,
getCachedData, getCachedData,
} from "../../slices/apiDataManager"; } from "../../slices/apiDataManager";
import { clearApiCacheKey } from "../../slices/apiCacheSlice"; import { clearApiCacheKey } from "../../slices/apiCacheSlice";
import {useMutation} from "@tanstack/react-query";
const mobileNumberRegex = /^[0-9]\d{9}$/; const mobileNumberRegex = /^[0-9]\d{9}$/;
const ManageEmployee = ({ employeeId, onClosed }) => { const ManageEmployee = ({ employeeId, onClosed,IsAllEmployee }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { mutate: updateEmployee, isPending } = useUpdateEmployee();
// const { employeeId } = useParams();
const { const {
employee, employee,
error, error,
loading: empLoading, loading: empLoading,
refetch
} = useEmployeeProfile(employeeId); } = useEmployeeProfile(employeeId);
useEffect(() => { useEffect(() => {
@ -128,7 +130,12 @@ const ManageEmployee = ({ employeeId, onClosed }) => {
.min(1, { message: "Phone Number is required" }) .min(1, { message: "Phone Number is required" })
.regex(mobileNumberRegex, { message: "Invalid phone number " }), .regex(mobileNumberRegex, { message: "Invalid phone number " }),
jobRoleId: z.string().min(1, { message: "Role is required" }), jobRoleId: z.string().min(1, { message: "Role is required" }),
}); } );
useEffect( () =>
{
refetch()
},[])
const { const {
register, register,
@ -162,39 +169,20 @@ const ManageEmployee = ({ employeeId, onClosed }) => {
}); });
const AadharNumberValue = watch("aadharNumber") || ""; const AadharNumberValue = watch("aadharNumber") || "";
const onSubmit = (data) => {
if (data.email === "") {
data.email = null;
}
const onSubmit = (data) => { updateEmployee({...data,IsAllEmployee},{
setLoading(true); onSuccess: () => {
if (data.email == "") { reset();
data.email = null; onClosed();
} },
EmployeeRepository.manageEmployee(data) });
.then((response) => { };
cacheData("employeeProfileInfo", data);
showToast(
`Employee details ${data.id == null ? "created" : "updated"
} successfully.`,
"success"
);
clearCacheKey("employeeListByProject");
clearCacheKey("allEmployeeList");
clearCacheKey("allInactiveEmployeeList");
clearCacheKey("employeeProfile");
setLoading(false);
reset();
// navigation("/employees");
onClosed();
})
.catch((error) => {
const message =
error?.response?.data?.message ||
error?.message ||
"Error occured during api calling";
showToast(message, "error");
setLoading(false);
});
};
useEffect(() => { useEffect(() => {
if (!loading && !error && employee) { if (!loading && !error && employee) {
@ -224,7 +212,7 @@ const ManageEmployee = ({ employeeId, onClosed }) => {
phoneNumber: currentEmployee.phoneNumber || "", phoneNumber: currentEmployee.phoneNumber || "",
jobRoleId: currentEmployee.jobRoleId?.toString() || "", jobRoleId: currentEmployee.jobRoleId?.toString() || "",
} }
: {} // Empty object resets the form : {}
); );
setCurrentAddressLength(currentEmployee?.currentAddress?.length || 0); setCurrentAddressLength(currentEmployee?.currentAddress?.length || 0);
setPermanentAddressLength(currentEmployee?.permanentAddress?.length || 0); setPermanentAddressLength(currentEmployee?.permanentAddress?.length || 0);
@ -232,44 +220,31 @@ const ManageEmployee = ({ employeeId, onClosed }) => {
return ( return (
<> <>
<form onSubmit={handleSubmit( onSubmit )} className="p-sm-0 p-2">
{/* <div className="c"> <div className="text-center"><p className="fs-6 fw-semibold"> {employee ? "Update Employee" : "Create Employee"}</p> </div>
{!currentEmployee && empLoading && employeeId && ( <div className="row mb-3">
<p>Loading Employee Data...</p> <div className="col-sm-4">
)} */} {" "}
<div className="form-text text-start">First Name</div>
<form onSubmit={handleSubmit(onSubmit)} className="p-sm-5 p-2 position-relative"> <input
{/* Cross button */} type="text"
<button name="FirstName"
type="button" {...register("firstName")}
className="btn-close position-absolute top-0 end-0 m-2" className="form-control form-control-sm"
aria-label="Close" id="firstName"
onClick={onClosed} placeholder="First Name"
></button> />
<div className="text-center"><p className="fs-6 fw-semibold"> {employee ? "Update Employee" : "Create Employee"}</p></div> {errors.firstName && (
<div className="row mb-3"> <div
<div className="col-sm-4"> className="danger-text text-start"
{" "} style={{ fontSize: "12px" }}
<div className="form-text text-start">First Name</div> >
<input {errors.firstName.message}
type="text" </div>
name="FirstName" )}
{...register("firstName")} </div>{" "}
className="form-control form-control-sm" <div className="col-sm-4">
id="firstName" <div className="form-text text-start">Middle Name</div>
placeholder="First Name"
/>
{errors.firstName && (
<div
className="danger-text text-start"
style={{ fontSize: "12px" }}
>
{errors.firstName.message}
</div>
)}
</div>{" "}
<div className="col-sm-4">
<div className="form-text text-start">Middle Name</div>
<input <input
type="text" type="text"
@ -622,9 +597,9 @@ const ManageEmployee = ({ employeeId, onClosed }) => {
aria-label="manage employee" aria-label="manage employee"
type="submit" type="submit"
className="btn btn-sm btn-primary" className="btn btn-sm btn-primary"
disabled={isloading} disabled={isPending}
> >
{isloading {isPending
? "Please Wait..." ? "Please Wait..."
: employeeId : employeeId
? "Update" ? "Update"
@ -634,8 +609,8 @@ const ManageEmployee = ({ employeeId, onClosed }) => {
<button <button
aria-label="manage employee" aria-label="manage employee"
type="reset" type="reset"
className="btn btn-sm btn-primary ms-2" className="btn btn-sm btn-secondary ms-2"
disabled={isloading} disabled={isPending}
> >
Clear Clear
</button> </button>

View File

@ -1,12 +1,13 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useRef, useState, useMemo } from "react";
import useMaster from "../../hooks/masterHook/useMaster";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { z } from "zod"; import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { RolesRepository } from "../../repositories/MastersRepository";
import { useEmployeeRoles } from "../../hooks/useEmployees";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { useEmployeeRoles, useUpdateEmployeeRoles } from "../../hooks/useEmployees";
import useMaster from "../../hooks/masterHook/useMaster";
import { changeMaster } from "../../slices/localVariablesSlice"; import { changeMaster } from "../../slices/localVariablesSlice";
import { RolesRepository } from "../../repositories/MastersRepository";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
const formSchema = z.object({ const formSchema = z.object({
@ -15,44 +16,17 @@ const formSchema = z.object({
const ManageRole = ( {employeeId, onClosed} ) => const ManageRole = ( {employeeId, onClosed} ) =>
{ {
const dispatch = useDispatch();
const disptach = useDispatch(); const formStateRef = useRef({});
useEffect(()=>{
disptach(changeMaster("Application Role"));
},[disptach])
const [isLoading, setIsLoading] = useState(false);
const { employeeRoles, loading } = useEmployeeRoles(employeeId);
const { data, loading: roleLoading } = useMaster();
const buildDefaultRoles = () => {
const defaults = {};
data.forEach((role) => {
const isRoleEnabled = employeeRoles?.some(
(empRole) => empRole.roleId === role.id
);
defaults[role.id] = isRoleEnabled;
});
return defaults;
};
const [initialRoles, setInitialRoles] = useState({});
useEffect(() => { useEffect(() => {
if (employeeRoles && data) { dispatch(changeMaster("Application Role"));
if (employeeRoles.length > 0) { }, [dispatch]);
const updatedRoles = buildDefaultRoles();
setInitialRoles(updatedRoles);
} else {
setInitialRoles({});
}
} else {
setInitialRoles({});
}
}, [employeeRoles, data]);
const { employeeRoles = [], loading: empLoading } = useEmployeeRoles(employeeId);
const { data: roles = [], loading: roleLoading } = useMaster();
const { const {
register, register,
handleSubmit, handleSubmit,
@ -60,167 +34,123 @@ useEffect(()=>{
reset, reset,
} = useForm({ } = useForm({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { selectedRole: {} },
}); });
const {
useEffect(() => { updateRoles,
if (!data) return; isPending : isLoading,
isError,
const updatedRoles = {}; error,
data.forEach((role) => { } = useUpdateEmployeeRoles({
const isRoleEnabled = employeeRoles?.some( onClose: onClosed,
resetForm: reset,
});
// Prepare default form values based on roles and current assignments
const selectedRoleDefaults = useMemo(() => {
const defaults = {};
roles.forEach((role) => {
const enabled = employeeRoles.some(
(empRole) => empRole.roleId === role.id && empRole.isEnabled (empRole) => empRole.roleId === role.id && empRole.isEnabled
); );
updatedRoles[role.id] = isRoleEnabled || false; defaults[role.id] = enabled;
}); });
return defaults;
setInitialRoles(updatedRoles); }, [roles, employeeRoles]);
reset({ // Avoid infinite loop by comparing previous form values
selectedRole: updatedRoles, useEffect(() => {
}); const prev = JSON.stringify(formStateRef.current);
}, [employeeRoles, data, reset]); const next = JSON.stringify(selectedRoleDefaults);
// const onSubmit = (formdata) => { if (prev !== next) {
// setIsLoading(true); formStateRef.current = selectedRoleDefaults;
// const result = []; reset({ selectedRole: selectedRoleDefaults });
}
// const selectedRoles = formdata.selectedRole; }, [selectedRoleDefaults, reset]);
// for (const [roleId, isChecked] of Object.entries(selectedRoles)) { const onSubmit = async (formData) => {
// const existingRole = employeeRoles?.find((role) => role.roleId === roleId); // setIsLoading(true);
// const wasChecked = !!existingRole?.isEnabled;
const updates = [];
// // Only push if the checked status has changed const selected = formData.selectedRole;
// if (isChecked !== wasChecked) {
// result.push({ for (const [roleId, isChecked] of Object.entries(selected)) {
// id: existingRole?.id || "00000000-0000-0000-0000-000000000000", const existing = employeeRoles.find((r) => r.roleId === roleId);
// employeeId, const wasChecked = !!existing?.isEnabled;
// roleId,
// isEnabled: isChecked,
// });
// }
// }
// if (result.length === 0) {
// showToast("No changes made", "info");
// setIsLoading(false);
// return;
// }
// console.log(result);
// RolesRepository.createEmployeeRoles(result)
// .then(() => {
// showToast("Roles updated successfully", "success");
// setIsLoading(false);
// reset();
// onClosed();
// })
// .catch((err) => {
// console.error(err);
// showToast(err.message, "error");
// setIsLoading(false);
// });
// };
const onSubmit = (formdata) => {
setIsLoading(true);
const result = [];
const selectedRoles = formdata.selectedRole;
for (const [roleId, isChecked] of Object.entries(selectedRoles)) {
const existingRole = employeeRoles?.find((role) => role.roleId === roleId);
const wasChecked = !!existingRole?.isEnabled;
// Only push if the checked status has changed
if (isChecked !== wasChecked) { if (isChecked !== wasChecked) {
result.push({ updates.push({
id: existingRole?.id || "00000000-0000-0000-0000-000000000000", id: existing?.id || "00000000-0000-0000-0000-000000000000",
employeeId, employeeId,
roleId, roleId,
isEnabled: isChecked, isEnabled: isChecked,
}); });
} }
} }
if (result.length === 0) { if (updates.length === 0) {
showToast("No changes made", "info"); showToast("No changes made", "info");
setIsLoading(false); // setIsLoading(false);
return; return;
} }
console.log(result); // try {
// await RolesRepository.createEmployeeRoles(updates);
RolesRepository.createEmployeeRoles(result) // showToast("Roles updated successfully", "success");
.then(() => { // reset();
showToast("Roles updated successfully", "success"); // onClosed();
setIsLoading(false); // } catch (err) {
reset(); // const message =
onClosed(); // err?.response?.data?.message || err?.message || "Error occurred while updating roles";
}) // showToast(message, "error");
.catch((error) => { // } finally {
const message = error?.response?.data?.message || error?.message || "Error occured during api calling" // setIsLoading(false);
showToast(message, "error"); // }
setIsLoading(false); updateRoles(updates);
});
}; };
const isLoadingData = roleLoading || empLoading;
return ( return (
<div
className={`modal fade `}
id="managerole-modal"
tabIndex="-1"
aria-hidden="true"
>
<div className="modal-dialog modal-simple modal-md d-flex align-items-center justify-content-center">
<div className="modal-content ">
<div className="modal-body">
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="text-start my-0"> <div className="text-start mb-3">
<p className="lead">Select Roles</p> <p className="lead">Select Roles</p>
</div> </div>
<div {isLoadingData ? (
className="d-flex flex-wrap justify-content-between align-items-center pb-5 " <p>Loading...</p>
style={{ maxHeight: "70vh", overflowY: "scroll" }} ) : (
> <div
{roleLoading && <p>Loading...</p>} className="d-flex flex-wrap justify-content-between pb-4"
{loading && <p>Loading...</p>} style={{ maxHeight: "70vh", overflowY: "auto" }}
{data && >
data.map((item) => ( {roles.map((role) => (
<div className="col-md-6 col-lg-4 mb-4" key={item.id}> <div className="col-md-6 col-lg-4 mb-3" key={role.id}>
<div className="form-check ms-2 text-start"> <div className="form-check ms-2 text-start">
<input <input
className="form-check-input" className="form-check-input"
type="checkbox" type="checkbox"
id={item.id} id={role.id}
{...register(`selectedRole.${item.id}`)} {...register(`selectedRole.${role.id}`)}
/> />
<label <label className="form-check-label" htmlFor={role.id}>
className="form-check-label text-bold" <small>{role.role || "--"}</small>
htmlFor={item.id}
>
<small>{item.role || "--"}</small>
</label> </label>
</div> </div>
</div> </div>
))} ))}
</div> </div>
{errors.selected && (
<div className="text-center">{errors.selected.message}</div>
)} )}
<div className="col-12 text-center"> {errors.selectedRole && (
<button type="submit" className="btn btn-sm btn-primary me-3"> <div className="text-danger text-center">
{isLoading ? "Please Wait" : "Submit"} {errors.selectedRole.message}
</div>
)}
<div className="text-center mt-3">
<button type="submit" className="btn btn-sm btn-primary me-3" disabled={isLoading}>
{isLoading ? "Please Wait..." : "Submit"}
</button> </button>
<button <button
type="reset" type="reset"
@ -232,11 +162,7 @@ useEffect(()=>{
</button> </button>
</div> </div>
</form> </form>
</div>
</div>
</div>
</div>
); );
}; };
export default ManageRole; export default ManageRole;

View File

@ -20,9 +20,9 @@ import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { MANAGE_PROJECT } from "../../utils/constants"; import { MANAGE_PROJECT } from "../../utils/constants";
const Header = () => { const Header = () => {
const { profile } = useProfile(); const {profile} = useProfile();
const location = useLocation(); const location = useLocation();
const dispatch = useDispatch(changeMaster("Job Role")); const dispatch = useDispatch();
const { data, loading } = useMaster(); const { data, loading } = useMaster();
const navigate = useNavigate(); const navigate = useNavigate();
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT); const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
@ -121,10 +121,10 @@ const Header = () => {
[fetchData,projectNames,selectedProject] [fetchData,projectNames,selectedProject]
); );
useEffect(() => { // useEffect(() => {
eventBus.on("assign_project_one", handler); // eventBus.on("assign_project_one", handler);
return () => eventBus.off("assign_project_one", handler); // return () => eventBus.off("assign_project_one", handler);
}, [handler]); // }, [handler]);
const newProjectHandler = useCallback( const newProjectHandler = useCallback(
async (msg) => { async (msg) => {
@ -140,10 +140,26 @@ const Header = () => {
[HasManageProjectPermission,projectNames] [HasManageProjectPermission,projectNames]
); );
// useEffect(() => {
// eventBus.on("project", newProjectHandler);
// return () => eventBus.off("project", newProjectHandler);
// }, [handler]);
useDispatch( () =>
{
dispatch(changeMaster("Job Role"))
},[])
useEffect(() => { useEffect(() => {
eventBus.on("project", newProjectHandler); eventBus.on("assign_project_one", handler);
return () => eventBus.off("project", newProjectHandler); eventBus.on("project", newProjectHandler);
}, [handler]);
return () => {
eventBus.off("assign_project_one", handler);
eventBus.off("project", newProjectHandler);
};
}, [handler, newProjectHandler]);
return ( return (
<nav <nav
className="layout-navbar container-fluid mb-3 navbar navbar-expand-xl navbar-detached align-items-center bg-navbar-theme" className="layout-navbar container-fluid mb-3 navbar navbar-expand-xl navbar-detached align-items-center bg-navbar-theme"
@ -167,7 +183,6 @@ const Header = () => {
<> <>
<i <i
className="rounded-circle bx bx-building-house bx-sm-lg bx-md" className="rounded-circle bx bx-building-house bx-sm-lg bx-md"
></i> ></i>
<div className="btn-group"> <div className="btn-group">
<button <button
@ -215,6 +230,7 @@ const Header = () => {
)} )}
</div> </div>
)} )}
{isProjectPath && (<span className=" fs-5 align-items-center"><i className="rounded-circle bx bx-building-house bx-sm-lg bx-md me-2"></i>{displayText}</span>)}
<ul className="navbar-nav flex-row align-items-center ms-md-auto"> <ul className="navbar-nav flex-row align-items-center ms-md-auto">
<li className="nav-item dropdown-shortcuts navbar-dropdown dropdown me-2 me-xl-0"> <li className="nav-item dropdown-shortcuts navbar-dropdown dropdown me-2 me-xl-0">

View File

@ -1,79 +1,49 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import moment from "moment"; import moment from "moment";
import { getProjectStatusName } from "../../utils/projectStatus"; import { getProjectStatusName } from "../../utils/projectStatus";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import {useProjectDetails, useUpdateProject} from "../../hooks/useProjects";
import { MANAGE_PROJECT } from "../../utils/constants"; import {useParams} from "react-router-dom";
import { cacheData, getCachedData } from "../../slices/apiDataManager"; import {useHasUserPermission} from "../../hooks/useHasUserPermission";
import {MANAGE_PROJECT} from "../../utils/constants";
import GlobalModel from "../common/GlobalModel";
import ManageProjectInfo from "./ManageProjectInfo"; import ManageProjectInfo from "./ManageProjectInfo";
import ProjectRepository from "../../repositories/ProjectRepository"; import {useQueryClient} from "@tanstack/react-query";
import showToast from "../../services/toastService"; const AboutProject = () =>
{
const AboutProject = ({ data }) => { const [ IsOpenModal, setIsOpenModal ] = useState( false )
const [CurrentProject, setCurrentProject] = useState(data); const {mutate: UpdateProjectDetails, isPending} = useUpdateProject( {
const [showModal, setShowModal] = useState(false); onSuccessCallback: () =>
useEffect(() => {
setCurrentProject(data);
}, [data]);
const manageProject = useHasUserPermission(MANAGE_PROJECT);
const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal(false);
const handleFormSubmit = (updatedProject, setLoading) => {
if ( CurrentProject?.id )
{ {
setIsOpenModal(false)
ProjectRepository.updateProject(CurrentProject.id, updatedProject)
.then((response) => {
const updatedProjectData = {
...CurrentProject,
...response.data,
building: CurrentProject.building,
};
setCurrentProject(updatedProject);
cacheData(`projectinfo-${CurrentProject.id}`, updatedProjectData);
const projects_list = getCachedData("projectslist");
if (projects_list) {
const updatedProjectsList = projects_list.map((project) =>
project.id === CurrentProject.id
? {
...project,
...response.data,
// tenant:project.tenant
}
: project
);
cacheData("projectslist", updatedProjectsList);
}
showToast( "Project updated successfully.", "success" );
setLoading(false);
setShowModal(false);
})
.catch((error) => {
showToast(error.message, "error");
});
} }
}; } )
const ClientQuery = useQueryClient()
const {projectId} = useParams();
const manageProject = useHasUserPermission(MANAGE_PROJECT);
const {projects_Details, isLoading, error,refetch} = useProjectDetails( projectId )
const handleFormSubmit = ( updatedProject ) =>
{
if ( projects_Details?.id )
{
UpdateProjectDetails({ projectId: projects_Details?.id,updatedData: updatedProject,
} );
refetch()
}
};
return ( return (
<> <>
<div {IsOpenModal && (
className={`modal fade ${showModal ? "show" : ""}`} <GlobalModel isOpen={IsOpenModal} closeModal={()=>setIsOpenModal(false)}>
tabIndex="-1" <ManageProjectInfo
role="dialog" project={projects_Details}
style={{ display: showModal ? "block" : "none" }} handleSubmitForm={handleFormSubmit}
aria-hidden={!showModal} onClose={() => setIsOpenModal( false )}
> isPending={isPending}
<ManageProjectInfo />
project={CurrentProject} </GlobalModel>
handleSubmitForm={handleFormSubmit} )}
onClose={handleClose} {projects_Details && (
></ManageProjectInfo> <>
</div>
{data && (
<div className="card mb-6"> <div className="card mb-6">
<div className="card-header text-start"> <div className="card-header text-start">
<h6 className="card-action-title mb-0"> <h6 className="card-action-title mb-0">
@ -87,19 +57,19 @@ const AboutProject = ({ data }) => {
<li className="d-flex align-items-center mb-3"> <li className="d-flex align-items-center mb-3">
<i className="bx bx-cog"></i> <i className="bx bx-cog"></i>
<span className="fw-medium mx-2">Name:</span>{" "} <span className="fw-medium mx-2">Name:</span>{" "}
<span>{data.name}</span> <span>{projects_Details.name}</span>
</li> </li>
<li className="d-flex align-items-center mb-3"> <li className="d-flex align-items-center mb-3">
<i className="bx bx-fingerprint"></i> <i className="bx bx-fingerprint"></i>
<span className="fw-medium mx-2">Nick Name:</span>{" "} <span className="fw-medium mx-2">Nick Name:</span>{" "}
<span> {data.shortName} </span> <span> {projects_Details.shortName} </span>
</li> </li>
<li className="d-flex align-items-center mb-3"> <li className="d-flex align-items-center mb-3">
<i className="bx bx-check"></i> <i className="bx bx-check"></i>
<span className="fw-medium mx-2">Start Date:</span>{" "} <span className="fw-medium mx-2">Start Date:</span>{" "}
<span> <span>
{data.startDate {projects_Details.startDate
? moment(data.startDate).format("DD-MMM-YYYY") ? moment(projects_Details.startDate).format("DD-MMM-YYYY")
: "N/A"} : "N/A"}
</span> </span>
</li> </li>
@ -107,31 +77,31 @@ const AboutProject = ({ data }) => {
<i className="bx bx-stop-circle"></i>{" "} <i className="bx bx-stop-circle"></i>{" "}
<span className="fw-medium mx-2">End Date:</span>{" "} <span className="fw-medium mx-2">End Date:</span>{" "}
<span> <span>
{data.endDate {projects_Details.endDate
? moment(data.endDate).format("DD-MMM-YYYY") ? moment(projects_Details.endDate).format("DD-MMM-YYYY")
: "N/A"} : "N/A"}
</span> </span>
</li> </li>
<li className="d-flex align-items-center mb-3"> <li className="d-flex align-items-center mb-3">
<i className="bx bx-trophy"></i> <i className="bx bx-trophy"></i>
<span className="fw-medium mx-2">Status:</span>{" "} <span className="fw-medium mx-2">Status:</span>{" "}
<span>{getProjectStatusName(data.projectStatusId)}</span> <span>{projects_Details?.projectStatus?.status}</span>
</li> </li>
<li className="d-flex align-items-center mb-3"> <li className="d-flex align-items-center mb-3">
<i className="bx bx-user"></i> <i className="bx bx-user"></i>
<span className="fw-medium mx-2">Contact:</span>{" "} <span className="fw-medium mx-2">Contact:</span>{" "}
<span>{data.contactPerson}</span> <span>{projects_Details.contactPerson}</span>
</li> </li>
<li className="d-flex flex-column align-items-start mb-3"> <li className="d-flex flex-column align-items-start mb-3">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<i className="bx bx-flag"></i> <i className="bx bx-flag"></i>
<span className="fw-medium mx-2">Address:</span> <span className="fw-medium mx-2">Address:</span>
{data.projectAddress?.length <= 20 && ( {projects_Details.projectAddress?.length <= 20 && (
<span>{data.projectAddress}</span> <span>{projects_Details.projectAddress}</span>
)} )}
</div> </div>
{data.projectAddress?.length > 20 && ( {projects_Details.projectAddress?.length > 20 && (
<div className="ms-4 text-start">{data.projectAddress}</div> <div className="ms-4 text-start">{projects_Details.projectAddress}</div>
)} )}
</li> </li>
@ -144,7 +114,7 @@ const AboutProject = ({ data }) => {
}`} }`}
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#edit-project-modal" data-bs-target="#edit-project-modal"
onClick={handleShow} onClick={()=>setIsOpenModal(true)}
> >
Modify Details Modify Details
</button> </button>
@ -152,10 +122,12 @@ const AboutProject = ({ data }) => {
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
)} </>
)}
{!data && <span>loading...</span>} {isLoading && <span>loading...</span>}
</> </>
); );
}; };

View File

@ -93,7 +93,7 @@ const AssignEmployeeTable = ({
onChange={handleRoleSelect} onChange={handleRoleSelect}
> >
{/* <option value="">Select a Job Role</option> */} {/* <option value="">Select a Job Role</option> */}
{jobRoles.map((role) => ( {jobRoles?.map((role) => (
<option key={role.id} value={role.id}> <option key={role.id} value={role.id}>
{role.name} {role.name}
</option> </option>

View File

@ -11,13 +11,12 @@ import { TasksRepository } from "../../repositories/ProjectRepository";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import { useProjectDetails } from "../../hooks/useProjects"; import { useProjectDetails } from "../../hooks/useProjects";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import { useCreateTask } from "../../hooks/useTasks";
const AssignTask = ({ assignData, onClose, setAssigned }) => { const AssignTask = ({ assignData, onClose, setAssigned }) => {
const maxPlanned = const maxPlanned =
assignData?.workItem?.workItem?.plannedWork - assignData?.workItem?.plannedWork -
assignData?.workItem?.workItem?.completedWork; assignData?.workItem?.completedWork;
// Zod schema for form validation
const schema = z.object({ const schema = z.object({
selectedEmployees: z selectedEmployees: z
.array(z.string()) .array(z.string())
@ -41,15 +40,16 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
const [isHelpVisibleTarget, setIsHelpVisibleTarget] = useState(false); const [isHelpVisibleTarget, setIsHelpVisibleTarget] = useState(false);
const helpPopupRefTarget = useRef(null); const helpPopupRefTarget = useRef(null);
const [isHelpVisible, setIsHelpVisible] = useState(false); const [isHelpVisible, setIsHelpVisible] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false); const { mutate: assignTask, isPending: isSubmitting } = useCreateTask({
onSuccessCallback: () => {
closedModel();
},
});
// Refs for Bootstrap Popovers
const infoRef = useRef(null); const infoRef = useRef(null);
const infoRef1 = useRef(null); const infoRef1 = useRef(null);
// Initialize Bootstrap Popovers on component mount
useEffect(() => { useEffect(() => {
// Check if Bootstrap is available globally
if (typeof bootstrap !== "undefined") { if (typeof bootstrap !== "undefined") {
if (infoRef.current) { if (infoRef.current) {
new bootstrap.Popover(infoRef.current, { new bootstrap.Popover(infoRef.current, {
@ -71,8 +71,7 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
} else { } else {
console.warn("Bootstrap is not available. Popovers might not function."); console.warn("Bootstrap is not available. Popovers might not function.");
} }
}, []); // Empty dependency array ensures this runs once on mount }, []);
// Redux state and hooks
const selectedProject = useSelector( const selectedProject = useSelector(
(store) => store.localVariables.projectId (store) => store.localVariables.projectId
); );
@ -82,14 +81,11 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
recallEmployeeData, recallEmployeeData,
} = useEmployeesAllOrByProjectId(selectedProject, false); } = useEmployeesAllOrByProjectId(selectedProject, false);
const dispatch = useDispatch(); const dispatch = useDispatch();
const { loading } = useMaster(); // Assuming this is for jobRoleData loading const { loading } = useMaster();
const jobRoleData = getCachedData("Job Role"); const {data:jobRoleData} = useMaster();
// Local component states
const [selectedRole, setSelectedRole] = useState("all"); const [selectedRole, setSelectedRole] = useState("all");
const [displayedSelection, setDisplayedSelection] = useState(""); // This state is not updated in the provided code, consider if it's still needed or how it should be updated const [displayedSelection, setDisplayedSelection] = useState("");
// React Hook Form setup
const { const {
handleSubmit, handleSubmit,
control, control,
@ -97,50 +93,43 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
watch, watch,
formState: { errors }, formState: { errors },
reset, reset,
trigger, // <--- IMPORTANT: Destructure 'trigger' here trigger,
} = useForm({ } = useForm({
defaultValues: { defaultValues: {
selectedEmployees: [], selectedEmployees: [],
description: "", description: "",
plannedTask: "", plannedTask: "",
}, },
resolver: zodResolver(schema), // Integrate Zod schema with react-hook-form resolver: zodResolver(schema),
}); });
// Handler for employee checkbox changes
const handleCheckboxChange = (event, user) => { const handleCheckboxChange = (event, user) => {
const isChecked = event.target.checked; const isChecked = event.target.checked;
let updatedSelectedEmployees = watch("selectedEmployees") || []; // Get current selected employees from form state let updatedSelectedEmployees = watch("selectedEmployees") || [];
if (isChecked) { if (isChecked) {
// Add employee if checked and not already in the list
if (!updatedSelectedEmployees.includes(user.id)) { if (!updatedSelectedEmployees.includes(user.id)) {
updatedSelectedEmployees = [...updatedSelectedEmployees, user.id]; updatedSelectedEmployees = [...updatedSelectedEmployees, user.id];
} }
} else { } else {
// Remove employee if unchecked updatedSelectedEmployees = updatedSelectedEmployees?.filter(
updatedSelectedEmployees = updatedSelectedEmployees.filter(
(id) => id !== user.id (id) => id !== user.id
); );
} }
// Update the form state with the new list of selected employees
setValue("selectedEmployees", updatedSelectedEmployees); setValue("selectedEmployees", updatedSelectedEmployees);
trigger("selectedEmployees"); // <--- IMPORTANT: Trigger validation here trigger("selectedEmployees");
}; };
// Effect to dispatch action for Job Role master data
useEffect(() => { useEffect(() => {
dispatch(changeMaster("Job Role")); dispatch(changeMaster("Job Role"));
// Cleanup function to reset selected role when component unmounts or dispatch changes
return () => setSelectedRole("all"); return () => setSelectedRole("all");
}, [dispatch]); }, [dispatch]);
// Handler for role filter change
const handleRoleChange = (event) => { const handleRoleChange = (event) => {
setSelectedRole(event.target.value); setSelectedRole(event.target.value);
}; };
// Filter employees based on selected role
const filteredEmployees = const filteredEmployees =
selectedRole === "all" selectedRole === "all"
? employees ? employees
@ -148,38 +137,21 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
(emp) => String(emp.jobRoleId || "") === selectedRole (emp) => String(emp.jobRoleId || "") === selectedRole
); );
// Form submission handler
const onSubmit = async (data) => {
const selectedEmployeeIds = data.selectedEmployees;
setIsSubmitting(true);
// Prepare taskTeam data (only IDs are needed for the backend based on previous context)
const taskTeamWithDetails = selectedEmployeeIds
.map((empId) => {
return empId; // Return just the ID as per previous discussions
})
.filter(Boolean); // Ensure no nulls if employee not found (though unlikely with current logic)
// Format data for API call const onSubmit = (data) => {
const selectedEmployeeIds = data.selectedEmployees;
const taskTeamWithDetails = selectedEmployeeIds?.map((empId) => empId)?.filter(Boolean);
const formattedData = { const formattedData = {
taskTeam: taskTeamWithDetails, taskTeam: taskTeamWithDetails,
plannedTask: data.plannedTask, plannedTask: data.plannedTask,
description: data.description, description: data.description,
assignmentDate: new Date().toISOString(), // Current date/time assignmentDate: new Date().toISOString(),
workItemId: assignData?.workItem?.workItem.id, workItemId: assignData?.workItem.id,
}; };
assignTask({payload:formattedData,workAreaId:assignData?.workArea?.id});
try {
await TasksRepository.assignTask(formattedData);
setIsSubmitting( false );
showToast("Task Assined Successfully.", "success");
closedModel();
} catch (error) {
setIsSubmitting(false);
showToast("Something went wrong. Please try again.", "error");
}
}; };
// Handler to close the modal and reset form
const closedModel = () => { const closedModel = () => {
reset(); reset();
onClose(); onClose();
@ -193,10 +165,10 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
<span className="text-dark text-start d-flex align-items-center flex-wrap form-text"> <span className="text-dark text-start d-flex align-items-center flex-wrap form-text">
<span className="me-2 m-0 font-bold">Work Location :</span> <span className="me-2 m-0 font-bold">Work Location :</span>
{[ {[
assignData?.building?.name, assignData?.building?.buildingName,
assignData?.floor?.floorName, assignData?.floor?.floorName,
assignData?.workArea?.areaName, assignData?.workArea?.areaName,
assignData?.workItem?.workItem?.activityMaster?.activityName, assignData?.workItem?.activityMaster?.activityName,
] ]
.filter(Boolean) // Filter out any undefined/null values .filter(Boolean) // Filter out any undefined/null values
.map((item, index, array) => ( .map((item, index, array) => (
@ -359,7 +331,7 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
"selectedEmployees", "selectedEmployees",
updatedSelected updatedSelected
); );
trigger("selectedEmployees"); // <--- IMPORTANT: Trigger validation on removing badge trigger("selectedEmployees");
}} }}
> >
<i className="icon-base bx bx-x icon-md "></i> <i className="icon-base bx bx-x icon-md "></i>
@ -376,11 +348,10 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
{!loading && errors.selectedEmployees && ( {!loading && errors.selectedEmployees && (
<div className="danger-text mt-1"> <div className="danger-text mt-1">
<p>{errors.selectedEmployees.message}</p>{" "} <p>{errors.selectedEmployees.message}</p>{" "}
{/* Use message from Zod schema */}
</div> </div>
)} )}
{/* Pending Task of Activity section */}
<div className="col-md text-start mx-0 px-0"> <div className="col-md text-start mx-0 px-0">
<div className="form-check form-check-inline mt-3 px-1"> <div className="form-check form-check-inline mt-3 px-1">
<label <label
@ -393,12 +364,12 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
htmlFor="inlineCheckbox1" htmlFor="inlineCheckbox1"
> >
<strong> <strong>
{assignData?.workItem?.workItem?.plannedWork - {assignData?.workItem?.plannedWork -
assignData?.workItem?.workItem?.completedWork} assignData?.workItem?.completedWork}
</strong>{" "} </strong>{" "}
<u> <u>
{ {
assignData?.workItem?.workItem?.activityMaster assignData?.workItem?.activityMaster
?.unitOfMeasurement ?.unitOfMeasurement
} }
</u> </u>

View File

@ -4,7 +4,8 @@ const Building = ({
toggleBuilding, toggleBuilding,
expandedBuildings, expandedBuildings,
getContent, getContent,
}) => { } ) =>
{
return ( return (
<React.Fragment key={building.id}> <React.Fragment key={building.id}>
<tr className="overflow-auto"> <tr className="overflow-auto">
@ -20,7 +21,7 @@ const Building = ({
> >
<div className="row table-responsive"> <div className="row table-responsive">
<h6 style={{ marginBottom: "0px", fontSize: "14px" }}> <h6 style={{ marginBottom: "0px", fontSize: "14px" }}>
{building.name} &nbsp; {building.buildingName} &nbsp;
{expandedBuildings.includes(building.id) ? ( {expandedBuildings.includes(building.id) ? (
<i className="bx bx-chevron-down"></i> <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 { useForm } from "react-hook-form";
import { z } from "zod"; import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import ProjectRepository from "../../../repositories/ProjectRepository";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { useProjectDetails } from "../../../hooks/useProjects";
import { getCachedData } from "../../../slices/apiDataManager"; import { getCachedData } from "../../../slices/apiDataManager";
import showToast from "../../../services/toastService"; import showToast from "../../../services/toastService";
import { useManageProjectInfra } from "../../../hooks/useProjects";
import useSelect from "../../common/useSelect";
// Zod validation schema
const buildingSchema = z.object({ const buildingSchema = z.object({
Id: z.string().optional(), Id: z.string().optional(),
name: z.string().min(1, "Building name is required"), name: z.string().min(1, "Building name is required"),
@ -18,194 +17,165 @@ const buildingSchema = z.object({
.max(160, "Description cannot exceed 160 characters"), .max(160, "Description cannot exceed 160 characters"),
}); });
const BuildingModel = ({ const BuildingModel = ({ project, onClose, editingBuilding = null }) => {
project,
onClose,
onSubmit,
clearTrigger,
onClearComplete,
editingBuilding = null,
}) => {
const selectedProject = useSelector( const selectedProject = useSelector(
(store) => store.localVariables.projectId (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 { const {
register, register,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors },
setValue, setValue,
watch,
reset, reset,
getValues,
} = useForm({ } = useForm({
resolver: zodResolver(buildingSchema), resolver: zodResolver(buildingSchema),
defaultValues: formData, // Set default values from formData state defaultValues: {
});
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({
Id: "0", Id: "0",
name: "", name: "",
description: "", description: "",
}); },
if (data.Id !== null) { });
showToast("Building updated successfully.", "success"); const watchedId = watch("Id");
} else {
showToast("Building created successfully.", "success"); 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(() => { useEffect(() => {
if(projects_Details){ if (!watchedId || watchedId === "0") {
setBuildings(projects_Details.data?.buildings); 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 ( return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user"> <form onSubmit={handleSubmit(onSubmitHandler)} className="row g-2">
<div className="modal-content"> <h5 className="text-center mb-2">Manage Buildings </h5>
<div className="modal-body"> <div className="col-12">
<button <label className="form-label">Select Building</label>
type="button" <select
className="btn-close" {...register("Id")}
data-bs-dismiss="modal" className="select2 form-select form-select-sm"
aria-label="Close" >
onClick={() => { <option value="0">Add New Building</option>
onClose(); {sortedBuildings.length > 0 ? (
reset(); // Call reset here sortedBuildings.map((b) => (
}} <option key={b.id} value={b.id}>
></button> {b.buildingName}
<h5 className="text-center mb-2"> </option>
Manage Buildings - {project?.name} ))
</h5> ) : (
<form onSubmit={handleSubmit(onSubmitHandler)} className="row g-2"> <option disabled>No buildings found</option>
<div className="col-12"> )}
<label className="form-label">Select Building</label> </select>
<select {errors.Id && <span className="danger-text">{errors.Id.message}</span>}
{...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}
</option>
))
) : (
<option disabled>No buildings found</option>
)}
</select>
{errors.Id && (
<span className="danger-text">{errors.Id.message}</span>
)}
</div>
<div className="col-12">
<label className="form-label">
{formData.id ? "Rename Building Name" : "New Building Name"}
</label>
<input
{...register("name")}
type="text"
className="form-control form-control-sm"
/>
{errors.name && (
<span className="danger-text">{errors.name.message}</span>
)}
</div>
<div className="col-12">
<label className="form-label">Description</label>
<textarea
{...register("description")}
maxLength="160"
rows="5"
className="form-control form-control-sm"
/>
{errors.description && (
<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")
? "Edit Building"
: "Add Building"}
</button>
<button
type="reset"
className="btn btn-sm btn-label-secondary"
data-bs-dismiss="modal"
aria-label="Close"
onClick={() => {
onClose();
reset(); // Call reset here
}}
>
Cancel
</button>
</div>
</form>
</div>
</div> </div>
</div>
{/* Name */}
<div className="col-12">
<label className="form-label">
{watchedId !== "0" ? "Rename Building Name" : "New Building Name"}
</label>
<input
{...register("name")}
type="text"
className="form-control form-control-sm"
/>
{errors.name && (
<span className="danger-text">{errors.name.message}</span>
)}
</div>
{/* Description */}
<div className="col-12">
<label className="form-label">Description</label>
<textarea
{...register("description")}
rows="5"
maxLength="160"
className="form-control form-control-sm"
/>
{errors.description && (
<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"
disabled={isPending}
>
{isPending
? "Please wait..."
: watchedId !== "0"
? "Edit Building"
: "Add Building"}
</button>
<button
type="reset"
className="btn btn-sm btn-label-secondary"
disabled={isPending}
onClick={() => {
onClose();
reset();
}}
>
Cancel
</button>
</div>
</form>
); );
}; };
export default BuildingModel; 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 { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { useActivitiesMaster, useWorkCategoriesMaster } from "../../../hooks/masterHook/useMaster";
import { useProjectDetails } from "../../../hooks/useProjects";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import ProjectRepository from "../../../repositories/ProjectRepository";
import { import {
cacheData, useActivitiesMaster,
clearCacheKey, useWorkCategoriesMaster,
getCachedData, } from "../../../hooks/masterHook/useMaster";
} from "../../../slices/apiDataManager"; import { useManageTask } from "../../../hooks/useProjects";
import { cacheData, getCachedData } from "../../../slices/apiDataManager";
import { refreshData } from "../../../slices/localVariablesSlice"; import { refreshData } from "../../../slices/localVariablesSlice";
import showToast from "../../../services/toastService"; import showToast from "../../../services/toastService";
@ -20,18 +18,13 @@ const taskSchema = z
activityID: z.string().min(1, "Activity is required"), activityID: z.string().min(1, "Activity is required"),
workCategoryId: z.string().min(1, "Work Category is required"), workCategoryId: z.string().min(1, "Work Category is required"),
plannedWork: z.number().min(1, "Planned Work must be greater than 0"), plannedWork: z.number().min(1, "Planned Work must be greater than 0"),
completedWork: z.number().min( 0, "Completed Work must be greater than 0" ), completedWork: z.number().min(0, "Completed Work must be ≥ 0"),
comment:z.string() comment: z.string(),
}) })
.refine( .refine((data) => data.completedWork <= data.plannedWork, {
(data) => message: "Completed Work cannot be greater than Planned Work",
data.completedWork === undefined || path: ["completedWork"],
data.completedWork <= data.plannedWork, });
{
message: "Completed Work cannot be greater than Planned Work",
path: ["completedWork"], // error will show next to this field
}
);
const EditActivityModal = ({ const EditActivityModal = ({
workItem, workItem,
@ -39,29 +32,12 @@ const EditActivityModal = ({
building, building,
floor, floor,
onClose, onClose,
}) => { } ) =>
const selectedProject = useSelector( {
(store) => store.localVariables.projectId
); const { activities, loading: loadingActivities } = useActivitiesMaster();
const defaultModel = { const { categories, loading: loadingCategories } = useWorkCategoriesMaster();
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 [selectedActivity, setSelectedActivity] = useState(null); const [selectedActivity, setSelectedActivity] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [activityData, setActivityData] = useState([]);
const [categoryData, setCategoryData] = useState([]);
const dispatch = useDispatch();
const { const {
register, register,
@ -73,359 +49,214 @@ const EditActivityModal = ({
watch, watch,
} = useForm({ } = useForm({
resolver: zodResolver(taskSchema), resolver: zodResolver(taskSchema),
defaultValues: defaultModel, defaultValues: {
activityID: "",
workCategoryId: "",
plannedWork: 0,
completedWork: 0,
comment: "",
},
}); });
const { mutate: UpdateTask, isPending } = useManageTask({
const handleActivityChange = (e) => { onSuccessCallback: (response) =>
const selectedId = String(e.target.value);
const selected = activityData.find((a) => a.id === selectedId);
setSelectedActivity(selected || null);
setValue("activityID", selectedId);
};
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 ) =>
{ {
setIsSubmitting(true) showToast( response?.message, "success" )
const updatedProject = { ...projects_Details }; onClose()
const finalData = { }
} );
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(() => {
if (!workItem) return;
console.log(workItem)
reset({
activityID: String(
workItem?.workItem?.activityId || workItem?.activityMaster?.id
),
workCategoryId: String(
workItem?.workItem?.workCategoryId || workItem?.workCategoryMaster?.id
),
plannedWork:
workItem?.workItem?.plannedWork || workItem?.plannedWork || 0,
completedWork:
workItem?.workItem?.completedWork || workItem?.completedWork || 0,
comment: workItem?.workItem?.description || workItem?.description || "",
});
}, [workItem?.id,selectedActivity]);
useEffect(() => {
const selected = activities?.find((a) => a.id === activityID);
setSelectedActivity( selected || null );
}, [activityID, activities]);
const onSubmitForm = (data) =>
{
const payload = {
...data, ...data,
id: workItem?.workItem?.id ?? workItem?.id, id: workItem?.workItem?.id ?? workItem?.id,
buildingID: building?.id, buildingID: building?.id,
floorId: floor?.id, floorId: floor?.id,
workAreaId: workArea?.id, workAreaId: workArea?.id,
}; };
UpdateTask([payload])
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];
})(),
}
: 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 ( return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user"> <form className="row g-2 p-2 p-md-1" onSubmit={handleSubmit(onSubmitForm)}>
<div className="modal-content"> <div className="text-center mb-1">
<div className="modal-body"> <h5 className="mb-1">Manage Task</h5>
<div className="row"> </div>
<button
type="button"
className="btn-close"
aria-label="Close"
onClick={onClose}
/>
<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>
<input
type="text"
className="form-control form-control-sm"
value={building?.name}
disabled
/>
</div>
{/* Select Floor */} <div className="row g-2">
<div className="col-6 col-md-6"> <div className="col-12 col-md-6">
<label className="form-label" htmlFor="floorId"> <label className="form-label">Select Building</label>
Select Floor <input
</label> className="form-control form-control-sm"
value={building?.buildingName}
disabled
/>
</div>
<input <div className="col-12 col-md-6">
type="text" <label className="form-label">Select Floor</label>
className="form-control form-control-sm" <input
value={floor?.floorName} className="form-control form-control-sm"
disabled value={floor?.floorName}
/> disabled
</div> />
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="workAreaId">
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>
<select
id="activityID"
className="form-select form-select-sm"
{...register("activityID")}
>
{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}
</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>
<select
id="workCategoryId"
className="form-select form-select-sm"
{...register("workCategoryId")}
>
{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}
</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>
<input
{...register("plannedWork", { valueAsNumber: true })}
type="number"
className="form-control form-control-sm me-2"
placeholder="Planned Work"
/>
{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>
<input
{...register("completedWork", { valueAsNumber: true })}
type="number"
className="form-control form-control-sm me-2"
placeholder="Completed Work"
disabled={getValues("completedWork") > 0}
/>
{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>
<input
type="text"
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"
/>
{errors.comment && (
<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>
<button
type="button"
className="btn btn-sm btn-label-secondary"
onClick={onClose}
>
Cancel
</button>
</div>
</form>
</div>
</div> </div>
</div> </div>
</div>
<div className="col-12">
<label className="form-label">Select Work Area</label>
<input
className="form-control form-control-sm"
value={workArea?.areaName}
disabled
/>
</div>
<div className="col-12">
<label className="form-label">Select Activity</label>
<select
{...register("activityID")}
className="form-select form-select-sm"
>
<option disabled>Select Activity</option>
{loadingActivities ? (
<option>Loading...</option>
) : (
sortedActivities.map((a) => (
<option key={a.id} value={a.id}>
{a.activityName}
</option>
))
)}
</select>
{errors.activityID && (
<p className="danger-text">{errors.activityID.message}</p>
)}
</div>
<div className="col-12">
<label className="form-label">Select Work Category</label>
<select
{...register("workCategoryId")}
className="form-select form-select-sm"
>
<option disabled>Select Category</option>
{loadingCategories ? (
<option>Loading...</option>
) : (
sortedCategories.map((c) => (
<option key={c.id} value={c.id}>
{c.name}
</option>
))
)}
</select>
{errors.workCategoryId && (
<p className="danger-text">{errors.workCategoryId.message}</p>
)}
</div>
<div className="col-5">
<label className="form-label">Planned Work</label>
<input
{...register("plannedWork", { valueAsNumber: true })}
type="number"
className="form-control form-control-sm"
/>
{errors.plannedWork && (
<p className="danger-text">{errors.plannedWork.message}</p>
)}
</div>
<div className="col-5">
<label className="form-label">Completed Work</label>
<input
{...register("completedWork", { valueAsNumber: true })}
type="number"
disabled={getValues("completedWork") > 0}
className="form-control form-control-sm"
/>
{errors.completedWork && (
<p className="danger-text">{errors.completedWork.message}</p>
)}
</div>
<div className="col-2">
<label className="form-label">Unit</label>
<input
className="form-control form-control-sm"
disabled
value={selectedActivity?.unitOfMeasurement || ""}
/>
</div>
<div className="col-12">
<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>
<div className="col-12 text-center">
<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>
); );
}; };

View File

@ -16,13 +16,7 @@ const Floor = ({ floor, workAreas, forBuilding }) => {
<tr> <tr>
<td colSpan="4" className="text-start table-cell"> <td colSpan="4" className="text-start table-cell">
<div className="row ps-2"> <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="col-12 ps-8">
<div className="row"> <div className="row">
<div className="d-flex col-5"> <div className="d-flex col-5">

View File

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

View File

@ -11,7 +11,7 @@ import {
getCachedData, getCachedData,
} from "../../../slices/apiDataManager"; } from "../../../slices/apiDataManager";
const InfraTable = ({ buildings, projectId, signalRHandler }) => { const InfraTable = ({ buildings, projectId}) => {
const [projectBuilding, setProjectBuilding] = useState([]); const [projectBuilding, setProjectBuilding] = useState([]);
const [expandedBuildings, setExpandedBuildings] = useState([]); const [expandedBuildings, setExpandedBuildings] = useState([]);
const [showFloorModal, setShowFloorModal] = useState(false); const [showFloorModal, setShowFloorModal] = useState(false);
@ -100,14 +100,7 @@ const InfraTable = ({ buildings, projectId, signalRHandler }) => {
No floors have been added yet. Start by adding floors to manage No floors have been added yet. Start by adding floors to manage
this building. this building.
</p> </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> </div>
</td> </td>
</tr> </tr>
@ -120,34 +113,34 @@ const InfraTable = ({ buildings, projectId, signalRHandler }) => {
} }
}, [buildings]); }, [buildings]);
const handler = useCallback( // const handler = useCallback(
(msg) => { // (msg) => {
if (msg.projectIds.some((item) => item == projectId)) { // if (msg.projectIds.some((item) => item == projectId)) {
try { // try {
ProjectRepository.getProjectByprojectId(projectId) // ProjectRepository.getProjectByprojectId(projectId)
.then((response) => { // .then((response) => {
cacheData("projectInfo", { // cacheData("projectInfo", {
projectId: projectId, // projectId: projectId,
data: response.data, // data: response.data,
}); // });
setProjectBuilding(response?.data?.buildings); // setProjectBuilding(response?.data?.buildings);
signalRHandler?.(response?.data); // signalRHandler?.(response?.data);
showToast(msg.message, "info"); // showToast(msg.message, "info");
}) // })
.catch((error) => { // .catch((error) => {
console.error(error); // console.error(error);
}); // });
} catch (e) { // } catch (e) {
console.error(e); // console.error(e);
} // }
} // }
}, // },
[buildings] // [buildings]
); // );
useEffect(() => { // useEffect(() => {
eventBus.on("infra", handler); // eventBus.on("infra", handler);
return () => eventBus.off("infra", handler); // return () => eventBus.off("infra", handler);
}, [handler]); // }, [handler]);
return ( return (
<div> <div>

View File

@ -1,11 +1,13 @@
import React, { useState, useEffect } from "react"; import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { import {
useActivitiesMaster, useActivitiesMaster,
useWorkCategoriesMaster, useWorkCategoriesMaster,
} from "../../../hooks/masterHook/useMaster"; } from "../../../hooks/masterHook/useMaster";
import { useManageTask } from "../../../hooks/useProjects";
import showToast from "../../../services/toastService";
const taskSchema = z.object({ const taskSchema = z.object({
buildingID: z.string().min(1, "Building is required"), buildingID: z.string().min(1, "Building is required"),
@ -14,433 +16,267 @@ const taskSchema = z.object({
activityID: z.string().min(1, "Activity is required"), activityID: z.string().min(1, "Activity is required"),
workCategoryId: z.string().min(1, "Work Category is required"), workCategoryId: z.string().min(1, "Work Category is required"),
plannedWork: z.number().min(1, "Planned Work must be greater than 0"), plannedWork: z.number().min(1, "Planned Work must be greater than 0"),
completedWork: z.number().min( 0, "Completed Work must be greater than 0" ), completedWork: z.number().min(0, "Completed Work must be 0 or more"),
comment:z.string(), comment: z.string(),
}); });
const defaultModel = { const defaultModel = {
id: null, id: null,
buildingID: "", // Changed from "0" buildingID: "",
floorId: "", // Changed from "0" floorId: "",
workAreaId: "", // Changed from "0" workAreaId: "",
activityID: "", // Changed from null activityID: "",
workCategoryId: "", // Kept as empty workCategoryId: "",
plannedWork: 0, plannedWork: 0,
completedWork: 0, completedWork: 0,
comment: "" comment: "",
}; };
const TaskModel = ({ project, onSubmit, onClose }) => {
const TaskModel = ({ const { activities, loading: activityLoading } = useActivitiesMaster();
project, const { categories, categoryLoading } = useWorkCategoriesMaster();
onSubmit,
clearTrigger,
onClearComplete,
onClose,
}) => {
const [formData, setFormData] = useState(defaultModel);
const [selectedBuilding, setSelectedBuilding] = useState(null);
const [selectedFloor, setSelectedFloor] = useState(null);
const [selectedWorkArea, setSelectedWorkArea] = useState(null);
const [selectedActivity, setSelectedActivity] = useState(null);
const [selectedCategory, setSelectedCategory] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [activityData, setActivityData] = useState([]);
const [categoryData, setCategoryData] = useState([]);
const { activities, loading, error } = useActivitiesMaster();
const { categories, categoryLoading, categoryError } =
useWorkCategoriesMaster();
const { const {
register, register,
handleSubmit, handleSubmit,
formState: { errors }, watch,
reset,
setValue, setValue,
reset,
formState: { errors },
} = useForm({ } = useForm({
resolver: zodResolver(taskSchema), resolver: zodResolver(taskSchema),
defaultValues: defaultModel, defaultValues: defaultModel,
}); });
const [isSubmitting, setIsSubmitting] = useState(false);
const [activityData, setActivityData] = useState([]);
const [categoryData, setCategoryData] = useState([]);
const watchBuildingId = watch("buildingID");
const watchFloorId = watch("floorId");
const watchWorkAreaId = watch("workAreaId");
const watchActivityId = watch("activityID");
const watchCategoryId = watch("workCategoryId");
const selectedBuilding = project?.find((b) => b.id === watchBuildingId);
const selectedFloor = selectedBuilding?.floors?.find(
(f) => f.id === watchFloorId
);
const selectedWorkArea = selectedFloor?.workAreas?.find(
(w) => w.id === watchWorkAreaId
);
const selectedActivity = activityData?.find((a) => a.id === watchActivityId);
const selectedCategory = categoryData?.find((c) => c.id === watchCategoryId);
const { mutate: CreateTask, isPending } = useManageTask({
onSuccessCallback: ( response ) =>
{
showToast( response?.message, "success" )
onClose?.()
},
});
useEffect(() => { useEffect(() => {
resetForm(); reset(defaultModel);
}, []); }, []);
const handleBuildingChange = (e) => {
const { value } = e.target;
const building = project.buildings.find((b) => b.id === String(value));
setSelectedBuilding(building);
setSelectedFloor(null);
setSelectedWorkArea(null);
setSelectedActivity(null);
reset({
...defaultModel,
buildingID: value,
});
};
const handleFloorChange = (e) => {
const { value } = e.target;
const floor = selectedBuilding.floors.find((b) => b.id === String(value));
setSelectedFloor(floor);
setSelectedWorkArea(null);
setSelectedActivity(null);
reset((prev) => ({
...prev,
floorId: value,
workAreaId: "",
activityID: "",
workCategoryId: categoryData?.[0]?.id?.toString() ?? "",
}));
};
const handleWorkAreaChange = (e) => {
const { value } = e.target;
const workArea = selectedFloor.workAreas.find(
(b) => b.id === String(value)
);
setSelectedWorkArea(workArea);
reset((prev) => ({
...prev,
workAreaId: String(value),
}));
};
const handleActivityChange = (e) => {
const { value } = e.target;
const activity = activityData.find((b) => b.id === String(value));
setSelectedActivity(activity);
reset((prev) => ({
...prev,
activityID: String(value),
}));
};
const handleCategoryChange = (e) => {
const { value } = e.target;
const category = categoryData.find((b) => b.id === String(value));
setSelectedCategory(category);
reset((prev) => ({
...prev,
workCategoryId: String(value),
}));
};
const onSubmitForm = async (data) => {
setIsSubmitting(true);
await onSubmit(data);
setValue("plannedWork", 0);
setValue("completedWork", 0);
setValue("activityID", 0);
setValue("workCategoryId", categoryData?.[0]?.id?.toString() ?? "");
setIsSubmitting(false);
};
const resetForm = () => {
setFormData(defaultModel);
setSelectedBuilding(null);
setSelectedFloor(null);
setSelectedWorkArea(null);
setSelectedActivity(null);
setSelectedCategory(categoryData?.[0]?.id?.toString() ?? "");
reset(defaultModel);
};
useEffect(() => { useEffect(() => {
if (!loading && Array.isArray(activities) && activities.length > 0) { if (Array.isArray(activities) && activities.length > 0) {
setActivityData(activities); setActivityData(activities);
} }
}, [activities, loading]); }, [activities]);
useEffect(() => { useEffect(() => {
if ( if (Array.isArray(categories) && categories.length > 0) {
!categoryLoading && const sorted = [...categories].sort((a, b) =>
Array.isArray(categories) && (a?.name || "").localeCompare(b?.name || "")
categories.length > 0 );
) { setCategoryData(sorted);
const newCategories = categories?.slice()?.sort((a, b) => { setValue("workCategoryId", sorted?.[0]?.id?.toString() ?? "");
const nameA = a?.name || "";
const nameB = b?.name || "";
return nameA?.localeCompare(nameB);
});
setCategoryData(newCategories);
setSelectedCategory(newCategories[0])
} }
}, [categories, categoryLoading]); }, [categories]);
const onSubmitForm = async (data) => {
const payload = [data];
CreateTask(payload);
};
return ( return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user"> <form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
<div className="modal-content"> <div className="text-center mb-1">
<div className="modal-body"> <h5 className="mb-1">Manage Task</h5>
<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 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>
<select
id="buildingID"
className="form-select form-select-sm"
{...register("buildingID")}
onChange={handleBuildingChange}
>
<option value="">Select Building</option>
{project.buildings
?.filter((building) => building?.name) // Ensure valid name
?.sort((a, b) => a.name?.localeCompare(b.name))
?.map((building) => (
<option key={building.id} value={building.id}>
{building.name}
</option>
))}
{project.buildings?.filter((building) => building?.name)
.length === 0 && (
<option disabled>No buildings found</option>
)}
</select>
{errors.buildingID && (
<p className="danger-text">{errors.buildingID.message}</p>
)}
</div>
{/* Select Floor */}
{selectedBuilding && (
<div className="col-6 col-md-6">
<label className="form-label" htmlFor="floorId">
Select Floor
</label>
<select
id="floorId"
className="form-select form-select-sm"
{...register("floorId")}
onChange={handleFloorChange}
>
<option value="">Select Floor</option>
{selectedBuilding.floors
?.filter(
(floor) =>
floor?.floorName && Array.isArray(floor.workAreas)
)
?.sort((a, b) => a.floorName?.localeCompare(b.floorName))
?.map((floor) => (
<option key={floor.id} value={floor.id}>
{floor.floorName} - ({floor.workAreas.length} Work
Areas)
</option>
))}
{selectedBuilding.floors?.filter(
(floor) =>
floor?.floorName && Array.isArray(floor.workAreas)
).length === 0 && <option disabled>No floors found</option>}
</select>
{errors.floorId && (
<p className="danger-text">{errors.floorId.message}</p>
)}
</div>
)}
{selectedFloor && (
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="workAreaId">
Select Work Area
</label>
<select
id="workAreaId"
className="form-select form-select-sm"
{...register("workAreaId")}
onChange={handleWorkAreaChange}
>
<option value="">Select Work Area</option>
{selectedFloor.workAreas
?.filter((workArea) => workArea?.areaName)
?.sort((a, b) => a.areaName?.localeCompare(b.areaName))
?.map((workArea) => (
<option key={workArea.id} value={workArea.id}>
{workArea.areaName}
</option>
))}
{selectedFloor.workAreas?.filter(
(workArea) => workArea?.areaName
).length === 0 && (
<option disabled>No work areas found</option>
)}
</select>
{errors.workAreaId && (
<p className="danger-text">{errors.workAreaId.message}</p>
)}
</div>
)}
{selectedWorkArea && (
<div className="col-12 col-md-12">
<label className="form-label">Select Activity</label>
<select
id="activityID"
className="form-select form-select-sm"
{...register("activityID")}
onChange={handleActivityChange}
>
<option value="">Select Activity</option>
{activityData &&
activityData.length > 0 &&
activityData
?.slice()
?.sort((a, b) => {
const nameA = a?.activityName || "";
const nameB = b?.activityName || "";
return nameA?.localeCompare(nameB);
})
?.map((activity) => (
<option key={activity.id} value={activity.id}>
{activity.activityName ||
`Unnamed (id: ${activity.id})`}
</option>
))}
{!loading && activities.length === 0 && (
<option disabled>No activities available</option>
)}
{loading && <option disabled>Loading...</option>}
</select>
{errors.activityID && (
<p className="danger-text">{errors.activityID.message}</p>
)}
</div>
)}
{selectedWorkArea && (
<div className="col-12 col-md-12">
<label className="form-label">Select Work Category</label>
<select
id="workCategoryId"
className="form-select form-select-sm"
{...register("workCategoryId")}
onChange={handleCategoryChange}
>
{categoryData &&
categoryData.length > 0 &&
categoryData
?.map((category) => (
<option key={category.id} value={category.id}>
{category.name || `Unnamed (id: ${category.id})`}
</option>
))}
{!categoryLoading && categories.length === 0 && (
<option disabled>No activities available</option>
)}
{categoryLoading && <option disabled>Loading...</option>}
</select>
{errors.workCategoryId && (
<p className="danger-text">
{errors.workCategoryId.message}
</p>
)}
</div>
)}
{selectedActivity && selectedCategory && (
<div className="col-5 col-md-5">
<label className="form-label" htmlFor="plannedWork">
{formData.id !== "0" ? "Modify " : "Enter "} Planned Work
</label>
<input
{...register("plannedWork", { valueAsNumber: true })}
type="number"
className="form-control form-control-sm me-2"
placeholder="Planned Work"
/>
{errors.plannedWork && (
<p className="danger-text">{errors.plannedWork.message}</p>
)}
</div>
)}
{selectedActivity && selectedCategory && (
<div className="col-5 col-md-5">
<label className="form-label" htmlFor="completedWork">
{formData.id !== "0" ? "Modify " : "Enter "} Completed Work
</label>
<input
{...register("completedWork", { valueAsNumber: true })}
type="number"
className="form-control form-control-sm me-2"
placeholder="Completed Work"
/>
{errors.completedWork && (
<p className="danger-text">
{errors.completedWork.message}
</p>
)}
</div>
)}
{selectedActivity && selectedCategory && (
<div className="col-2 col-md-2">
<label className="form-label" htmlFor="unit">
Unit
</label>
<input
type="text"
disabled
className="form-control form-control-sm me-2"
value={selectedActivity?.unitOfMeasurement || ""}
/>
</div>
)}
{selectedActivity && selectedCategory && (
<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"
/>
{errors.comment && (
<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">
{isSubmitting ? "Please Wait.." : "Add Task"}
</button>
<button
type="button"
className="btn btn-sm btn-label-secondary"
onClick={onClose}
>
Cancel
</button>
</div>
</form>
</div>
</div>
</div> </div>
</div> <div className="col-6">
<label className="form-label">Select Building</label>
<select
className="form-select form-select-sm"
{...register("buildingID")}
>
<option value="">Select Building</option>
{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>
))}
</select>
{errors.buildingID && (
<p className="danger-text">{errors.buildingID.message}</p>
)}
</div>
{selectedBuilding && (
<div className="col-6">
<label className="form-label">Select Floor</label>
<select
className="form-select form-select-sm"
{...register("floorId")}
>
<option value="">Select Floor</option>
{selectedBuilding.floors
?.sort((a, b) => a.floorName.localeCompare(b.floorName))
?.map((f) => (
<option key={f.id} value={f.id}>
{f.floorName}
</option>
))}
</select>
{errors.floorId && (
<p className="danger-text">{errors.floorId.message}</p>
)}
</div>
)}
{selectedFloor && (
<div className="col-12">
<label className="form-label">Select Work Area</label>
<select
className="form-select form-select-sm"
{...register("workAreaId")}
>
<option value="">Select Work Area</option>
{selectedFloor.workAreas
?.sort((a, b) => a.areaName.localeCompare(b.areaName))
?.map((w) => (
<option key={w.id} value={w.id}>
{w.areaName}
</option>
))}
</select>
{errors.workAreaId && (
<p className="danger-text">{errors.workAreaId.message}</p>
)}
</div>
)}
{selectedWorkArea && (
<div className="col-12">
<label className="form-label">Select Activity</label>
<select
className="form-select form-select-sm"
{...register("activityID")}
>
<option value="">Select Activity</option>
{activityData.map((a) => (
<option key={a.id} value={a.id}>
{a.activityName}
</option>
))}
</select>
{errors.activityID && (
<p className="danger-text">{errors.activityID.message}</p>
)}
</div>
)}
{selectedWorkArea && (
<div className="col-12">
<label className="form-label">Select Work Category</label>
<select
className="form-select form-select-sm"
{...register("workCategoryId")}
>
{categoryData.map((c) => (
<option key={c.id} value={c.id}>
{c.name}
</option>
))}
</select>
{errors.workCategoryId && (
<p className="danger-text">{errors.workCategoryId.message}</p>
)}
</div>
)}
{selectedActivity && selectedCategory && (
<>
<div className="col-5">
<label className="form-label">Planned Work</label>
<input
type="number"
className="form-control form-control-sm"
{...register("plannedWork", { valueAsNumber: true })}
/>
{errors.plannedWork && (
<p className="danger-text">{errors.plannedWork.message}</p>
)}
</div>
<div className="col-5">
<label className="form-label">Completed Work</label>
<input
type="number"
className="form-control form-control-sm"
{...register("completedWork", { valueAsNumber: true })}
/>
{errors.completedWork && (
<p className="danger-text">{errors.completedWork.message}</p>
)}
</div>
<div className="col-2">
<label className="form-label">Unit</label>
<input
type="text"
className="form-control form-control-sm"
disabled
value={selectedActivity?.unitOfMeasurement || ""}
/>
</div>
</>
)}
{selectedActivity && selectedCategory && (
<div className="col-12">
<label className="form-label">Comment</label>
<textarea
className="form-control"
rows="2"
{...register("comment")}
/>
{errors.comment && (
<p className="danger-text">{errors.comment.message}</p>
)}
</div>
)}
<div className="col-12 text-center">
<button
type="submit"
className="btn btn-sm btn-primary me-3"
disabled={isSubmitting}
>
{isSubmitting ? "Please Wait..." : "Add Task"}
</button>
<button
type="button"
className="btn btn-sm btn-label-secondary"
onClick={onClose}
>
Cancel
</button>
</div>
</form>
); );
}; };
export default TaskModel; export default TaskModel;

View File

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

View File

@ -1,39 +1,31 @@
import React, { useState, useEffect } from "react"; import React, { useEffect, useState } from "react";
import { set, useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import showToast from "../../../services/toastService"; import showToast from "../../../services/toastService";
import { useManageProjectInfra } from "../../../hooks/useProjects";
import { useSelector } from "react-redux";
// Zod schema for form validation
const workAreaSchema = z.object({ const workAreaSchema = z.object({
id: z.string().nonempty("Floor is required"), id: z.string().optional(),
buildingId: z.string().refine((val) => val !== "0", {
buildingId: z.string().nonempty("Building is required"), message: "Building is required",
floorId: z.string().nonempty("Floor is required"), }),
areaName: z floorId: z.string().refine((val)=>val !== "0",{message:"Floor is required"}),
.string() areaName: z.string().min(3, "Work Area Name must be at least 3 characters"),
.nonempty("Work Area Name is required")
.min(3, "Name must be at least 3 characters long"),
}); });
// Default form data
const defaultModel = { const defaultModel = {
id: "0", id: "0",
areaName: "", buildingId: "0",
floorId: "0", floorId: "0",
areaName: "",
}; };
const WorkAreaModel = ({ const WorkAreaModel = ({ project, onSubmit, onClose }) => {
project,
onSubmit,
clearTrigger,
onClearComplete,
onClose,
}) => {
const [selectedBuilding, setSelectedBuilding] = useState(null); const [selectedBuilding, setSelectedBuilding] = useState(null);
const [selectedFloor, setSelectedFloor] = useState(null); const [selectedFloor, setSelectedFloor] = useState(null);
const [selectdWorkArea, setWorkArea] = useState(); const selectedProject = useSelector((store)=>store.localVariables.projectId)
const { const {
register, register,
handleSubmit, handleSubmit,
@ -42,259 +34,168 @@ const WorkAreaModel = ({
reset, reset,
watch, watch,
} = useForm({ } = useForm({
resolver: zodResolver(workAreaSchema), // Use Zod resolver for validation resolver: zodResolver(workAreaSchema),
defaultValues: defaultModel, defaultValues: defaultModel,
}); });
const floorId = watch("floorId"); // Watch the floorId for conditional rendering const watchBuildingId = watch("buildingId");
const watchFloorId = watch("floorId");
const watchWorkAreaId = watch("id");
const { mutate: ManageWorkArea, isPending } = useManageProjectInfra({
onSuccessCallback: (data, variables) => {
showToast(
watchWorkAreaId != "0"
? "Wrok Area updated Successfully"
: "Work Area created Successfully",
"success"
);
reset({ id: "0", buildingId: "0", areaName: "", floorId: "0" });
// onClose?.();
},
});
useEffect(() => { useEffect(() => {
if (clearTrigger) { const building = project?.find((b) => b.id === watchBuildingId);
reset(defaultModel); // Reset form to initial state setSelectedBuilding(building || null);
setSelectedBuilding(null);
setSelectedFloor(null);
onClearComplete();
}
}, [clearTrigger, onClearComplete, reset]);
const handleWorkAreaChange = (e) => { if (building) {
const { value } = e.target; const floor = building.floors?.find((f) => f.id === watchFloorId);
setSelectedFloor(floor || null);
if (value === "0") {
setValue("id", "0"); // Create New Work Area
setValue("areaName", ""); setValue("areaName", "");
setWorkArea(String(0));
} else {
const workArea = selectedFloor?.workAreas.find(
(b) => b.id === String(value)
);
if (workArea) {
setValue("id", String(workArea.id)); // Set id as a string
setValue("areaName", workArea.areaName);
setWorkArea(String(workArea.id));
}
}
};
const handleFloorChange = (e) => {
const { value } = e.target;
const floor = selectedBuilding?.floors.find((b) => b.id === String(value));
if (floor) {
setSelectedFloor(floor);
setValue("floorId", floor.id); // Update floorId
setValue("id", "0"); // Reset Work Area ID for new area creation
setValue("areaName", ""); // Reset Work Area Name when changing floor
} else { } else {
setSelectedFloor(null); setSelectedFloor(null);
setValue("floorId", "0"); setValue("floorId", "0");
setValue("id", "0"); // Reset Work Area ID setValue("areaName", "");
setValue("areaName", ""); // Reset Work Area Name }
}, [watchBuildingId, watchFloorId]);
const handleWrokAreaChange = (e) => {
const workAreaId = e.target.value;
setValue("id", workAreaId);
const area = selectedFloor?.workAreas.find((w) => w.id === workAreaId);
if (area) {
setValue("areaName", area.areaName);
} else {
setValue("areaName", "");
} }
}; };
const handleBuildingChange = (e) => { useEffect(() => {
const { value } = e.target; reset(defaultModel);
const building = project?.buildings.find((b) => b.id === String(value)); }, []);
setSelectedBuilding(building);
setSelectedFloor(null); // Reset selected floor on building change
reset(defaultModel); // Reset the form when a new building is selected
};
const onSubmitForm = (data) => { const onSubmitForm = ( data ) =>
let WorkArea = { {
id: data.id == "0" ? null : data.id, const payload = {
id: data.id === "0" ? null : data.id,
areaName: data.areaName, areaName: data.areaName,
floorId: data.floorId, floorId: data.floorId,
buildingId: data.buildingId, buildingId: data.buildingId,
}; };
onSubmit(WorkArea); let infraObject = [
{
building: null,
floor: null,
workArea: payload,
},
];
reset({ ManageWorkArea({ infraObject, projectId: selectedProject });
id: "0",
areaName: "",
});
if (WorkArea.id !== null) {
showToast("WorkArea updated successfully.", "success");
} else {
showToast("WorkArea created successfully.", "success");
}
};
const handleCancel = () => {
reset(defaultModel);
setSelectedFloor(null);
setSelectedBuilding(null);
onClose();
}; };
return ( return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user"> <form className="row g-2 p-2 p-md-1" onSubmit={handleSubmit(onSubmitForm)}>
<div className="modal-content"> <div className="text-center mb-1">
<div className="modal-body"> <h5 className="mb-1">Manage Work Area</h5>
<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 Work Area</h5>
</div>
<form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
{/* Building Selection */}
<div className="col-6 col-md-6">
<label className="form-label" htmlFor="buildingId">
Select Building
</label>
<select
id="buildingId"
name="buildingId"
className="select2 form-select form-select-sm"
{...register("buildingId")}
onChange={handleBuildingChange}
>
<option value="0">Select Building</option>
{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}
</option>
))}
{project?.buildings?.filter((building) => building?.name)
.length === 0 && (
<option disabled>No buildings found</option>
)}
</select>
{errors.buildingId && <span>{errors.buildingId.message}</span>}
</div>
{/* Floor Selection */}
{selectedBuilding && selectedBuilding.buildingId !== "0" && (
<div className="col-6 col-md-6">
<label className="form-label" htmlFor="floorId">
Select Floor
</label>
<select
id="floorId"
name="floorId"
className="select2 form-select form-select-sm"
{...register("floorId")}
onChange={handleFloorChange}
>
<option value="0">Select Floor</option>
{selectedBuilding.floors
?.filter((floor) => floor?.floorName)
?.sort((a, b) => {
const nameA = a.floorName || "";
const nameB = b.floorName || "";
return nameA?.localeCompare(nameB);
})
?.map((floor) => (
<option key={floor.id} value={floor.id}>
{floor.floorName}
</option>
))}
{selectedBuilding.floors?.filter(
(floor) => floor?.floorName
).length === 0 && <option disabled>No floors found</option>}
</select>
{errors.floorId && <span>{errors.floorId.message}</span>}
</div>
)}
{/* Work Area Selection or Creation */}
{floorId !== "0" && (
<>
<div className="col-12 col-md-12">
<label className="form-label">Select Work Area</label>
<select
id="workAreaId"
name="workAreaId"
className="select2 form-select form-select-sm"
{...register("id")}
onChange={handleWorkAreaChange}
>
<option value="0">Create New Work Area</option>
{selectedFloor?.workAreas
?.filter((workArea) => workArea?.areaName)
?.sort((a, b) => {
const nameA = a.areaName || "";
const nameB = b.areaName || "";
return nameA?.localeCompare(nameB);
})
?.map((workArea) => (
<option key={workArea.id} value={workArea.id}>
{workArea.areaName}
</option>
))}
{selectedFloor?.workAreas?.filter(
(workArea) => workArea?.areaName
).length === 0 && (
<option disabled>No work areas found</option>
)}
</select>
{errors.id && <span>{errors.id.message}</span>}
</div>
{/* Work Area Name Input */}
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="areaName">
{watch("id") === "0"
? "Enter Work Area Name"
: "Modify Work Area Name"}
</label>
<input
type="text"
id="areaName"
name="areaName"
className="form-control form-control-sm"
placeholder="Work Area"
{...register("areaName")}
/>
{errors.areaName && (
<span className="danger-text">
{errors.areaName.message}
</span>
)}
</div>
{/* Submit and Cancel Buttons */}
<div className="col-12 text-center">
<button
type="submit"
className="btn btn-sm btn-primary me-3"
>
{watch("id") === "0" ? "Add Work Area" : "Edit Work Area"}
</button>
<button
type="button"
className="btn btn-sm btn-label-secondary"
onClick={handleCancel}
data-bs-dismiss="modal"
aria-label="Close"
>
Cancel
</button>
</div>
</>
)}
</form>
</div>
</div>
</div> </div>
</div> <div className="col-12 col-sm-6">
<label className="form-label">Select Building</label>
<select
{...register("buildingId")}
className="form-select form-select-sm"
>
<option value="0">Select Building</option>
{project?.map((b) => (
<option key={b.id} value={b.id}>
{b.buildingName}
</option>
))}
</select>
{errors.buildingId && (
<p className="danger-text">{errors.buildingId.message}</p>
)}
</div>
{watchBuildingId !== "0" && (
<div className="col-12 col-sm-6">
<label className="form-label">Select Floor</label>
<select
{...register("floorId")}
className="form-select form-select-sm"
>
<option value="0">
{selectedBuilding?.floor?.length > 0
? "NO Floor Found"
: "Select Floor"}
</option>
{selectedBuilding?.floors?.map((f) => (
<option key={f.id} value={f.id}>
{f.floorName}
</option>
))}
</select>
{errors.floorId && (
<p className="danger-text">{errors.floorId.message}</p>
)}
</div>
)}
{watchFloorId !== "0" && (
<>
<div className="col-12">
<label className="form-label">Select Work Area</label>
<select
{...register("id")}
className="form-select form-select-sm"
onChange={handleWrokAreaChange}
>
<option value="0">Create New Work Area</option>
{selectedFloor?.workAreas?.length > 0 &&
selectedFloor?.workAreas?.map((w) => (
<option key={w.id} value={w.id}>
{w.areaName}
</option>
))}
</select>
</div>
<div className="col-12">
<label className="form-label">
{watchWorkAreaId === "0"
? "Enter Work Area Name"
: "Edit Work Area Name"}
</label>
<input
type="text"
className="form-control form-control-sm"
placeholder="Work Area"
{...register("areaName")}
/>
{errors.areaName && (
<p className="danger-text">{errors.areaName.message}</p>
)}
</div>
</>
)}
<div className="col-12 text-center">
<button type="submit" className="btn btn-sm btn-primary me-3" disabled={isPending}>
{isPending ? "Please Wait.." : watchWorkAreaId === "0" ? "Add Work Area" : "Update Work Area"}
</button>
<button type="button" className="btn btn-sm btn-label-secondary" disabled={isPending} onClick={onClose}>
Cancel
</button>
</div>
</form>
); );
}; };

View File

@ -10,16 +10,16 @@ import {
} from "../../../utils/constants"; } from "../../../utils/constants";
import ConfirmModal from "../../common/ConfirmModal"; import ConfirmModal from "../../common/ConfirmModal";
import ProjectRepository from "../../../repositories/ProjectRepository"; import ProjectRepository from "../../../repositories/ProjectRepository";
import { useProjectDetails } from "../../../hooks/useProjects"; import { useDeleteProjectTask, useProjectDetails } from "../../../hooks/useProjects";
import showToast from "../../../services/toastService"; import showToast from "../../../services/toastService";
import { import {
cacheData, cacheData,
clearCacheKey, clearCacheKey,
getCachedData, getCachedData,
} from "../../../slices/apiDataManager"; } from "../../../slices/apiDataManager";
import { useDispatch } from "react-redux";
import { refreshData } from "../../../slices/localVariablesSlice"; import { refreshData } from "../../../slices/localVariablesSlice";
import GlobalModel from "../../common/GlobalModel"; import GlobalModel from "../../common/GlobalModel";
import {useDeleteMasterItem} from "../../../hooks/masterHook/useMaster";
const WorkItem = ({ const WorkItem = ({
workItem, workItem,
@ -39,18 +39,24 @@ const WorkItem = ({
const ManageAndAssignTak = useHasUserPermission(ASSIGN_REPORT_TASK); const ManageAndAssignTak = useHasUserPermission(ASSIGN_REPORT_TASK);
const [loadingDelete, setLoadingDelete] = useState(false); const [loadingDelete, setLoadingDelete] = useState(false);
const project = getCachedData("projectInfo"); const project = getCachedData("projectInfo");
const dispatch = useDispatch();
const openModal = () => setIsModalOpen(true); const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false); const closeModal = () => setIsModalOpen( false );
const showModalDelete = () => setShowModal2(true);
const closeModalDelete = () => setShowModal2(false);
const getProgress = (planned, completed) => { const getProgress = (planned, completed) => {
return (completed * 100) / planned + "%"; return (completed * 100) / planned + "%";
}; };
const {mutate:DeleteTask,isPending } = useDeleteProjectTask(() => {
closeModalDelete?.();
});
const handleAssignTask = () => { const handleAssignTask = () => {
setItemName(""); setItemName("");
}; };
useEffect(() => { useEffect(() => {
setNewWorkItem(workItem); setNewWorkItem(workItem);
}, [workItem]); }, [workItem]);
@ -79,17 +85,15 @@ const WorkItem = ({
tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el)); tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el));
}, []); }, []);
const showModal1 = () => setShowModal(true);
const closeModal1 = () => setShowModal(false);
const showModalDelete = () => setShowModal2(true);
const closeModalDelete = () => setShowModal2(false);
const handleSubmit = async () => { const handleSubmit = async () => {
setLoadingDelete(true);
let WorkItemId = workItem.workItemId || workItem.id; let WorkItemId = workItem.workItemId || workItem.id;
deleteHandleTask(WorkItemId); DeleteTask({workItemId:WorkItemId,workAreaId:forWorkArea?.id})
setLoadingDelete(false);
closeModalDelete();
}; };
const PlannedWork = const PlannedWork =
@ -105,21 +109,15 @@ const WorkItem = ({
)} )}
{showModal && ( {showModal && (
<div <GlobalModel isOpen={showModal} size="lg" closeModal={()=>setShowModal(false)}>
className={`modal fade ${showModal ? "show" : ""}`}
tabIndex="-1"
role="dialog"
style={{ display: showModal ? "block" : "none" }}
aria-hidden={!showModal}
>
<EditActivityModal <EditActivityModal
workItem={workItem} workItem={workItem}
workArea={forWorkArea} workArea={forWorkArea}
building={forBuilding} building={forBuilding}
floor={forFloor} floor={forFloor}
onClose={closeModal1} onClose={()=>setShowModal(false)}
/> />
</div> </GlobalModel>
)} )}
{showModal2 && ( {showModal2 && (
@ -169,7 +167,7 @@ const WorkItem = ({
: "NA"} : "NA"}
</td> </td>
{/* Category - visible on medium and above */}
<td className="text-center table-cell-small d-none d-md-table-cell"> <td className="text-center table-cell-small d-none d-md-table-cell">
<span className="fw-light"> <span className="fw-light">
{hasWorkItem {hasWorkItem
@ -204,7 +202,6 @@ const WorkItem = ({
: "NA"} : "NA"}
</td> </td>
{/* Progress Bar - always visible */}
<td className="text-center " style={{ width: "15%" }}> <td className="text-center " style={{ width: "15%" }}>
<div className="progress p-0"> <div className="progress p-0">
<div <div
@ -231,7 +228,6 @@ const WorkItem = ({
</div> </div>
</td> </td>
{/* Actions - always visible */}
{(ManageInfra || {(ManageInfra ||
(!projectId && (!projectId &&
ManageAndAssignTak && ManageAndAssignTak &&
@ -255,7 +251,7 @@ const WorkItem = ({
<i <i
className="bx bxs-edit text-secondary cursor-pointer" className="bx bxs-edit text-secondary cursor-pointer"
title="Edit" title="Edit"
onClick={showModal1} onClick={()=>setShowModal(true)}
role="button" role="button"
></i> ></i>
<i <i
@ -297,7 +293,7 @@ const WorkItem = ({
<li> <li>
<a <a
className="dropdown-item d-flex align-items-center" className="dropdown-item d-flex align-items-center"
onClick={showModal1} onClick={()=>setShowModal(true) }
> >
<i className="bx bxs-edit text-secondary me-2"></i> Edit <i className="bx bxs-edit text-secondary me-2"></i> Edit
</a> </a>

View File

@ -3,19 +3,6 @@ import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
// const currentDate = new Date().toISOString().split("T")[0];
const ACTIVE_STATUS_ID = "b74da4c2-d07e-46f2-9919-e75e49b12731";
const DEFAULT_EMPTY_STATUS_ID = "00000000-0000-0000-0000-000000000000";
/**
* Formats a given date string into 'YYYY-MM-DD' format.
* If the date is invalid or not provided, it defaults to the current date.
* @param {string} date - The date string to format.
* @returns {string} The formatted date string.
*/
const currentDate = new Date().toLocaleDateString('en-CA'); const currentDate = new Date().toLocaleDateString('en-CA');
const formatDate = (date) => { const formatDate = (date) => {
if (!date) { if (!date) {
@ -25,15 +12,16 @@ const formatDate = (date) => {
if (isNaN(d.getTime())) { if (isNaN(d.getTime())) {
return currentDate; return currentDate;
} }
// return d.toISOString().split("T")[0];
return d.toLocaleDateString('en-CA'); return d.toLocaleDateString('en-CA');
}; };
const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => { const ManageProjectInfo = ({ project, handleSubmitForm, onClose,isPending }) => {
const [CurrentProject, setCurrentProject] = useState(); const [CurrentProject, setCurrentProject] = useState();
const [isloading, setLoading] = useState(false);
const [addressLength, setAddressLength] = useState(0); const [addressLength, setAddressLength] = useState(0);
const maxAddressLength = 500; const maxAddressLength = 500;
const ACTIVE_STATUS_ID = "b74da4c2-d07e-46f2-9919-e75e49b12731";
const DEFAULT_EMPTY_STATUS_ID = "00000000-0000-0000-0000-000000000000";
const projectSchema = z const projectSchema = z
.object({ .object({
...(project?.id ? { id: z.string().optional() } : {}), ...(project?.id ? { id: z.string().optional() } : {}),
@ -105,18 +93,15 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
reset( reset(
project project
? { ? {
id: project?.id || "", id: project?.id || "",
name: project?.name || "", name: project?.name || "",
shortName: project?.shortName || "", shortName: project?.shortName || "",
contactPerson: project?.contactPerson || "", contactPerson: project?.contactPerson || "",
projectAddress: project?.projectAddress || "", projectAddress: project?.projectAddress || "",
startDate: formatDate(project?.startDate) || "", startDate: formatDate(project?.startDate) || "",
endDate: formatDate(project?.endDate) || "", endDate: formatDate(project?.endDate) || "",
// projectStatusId: String(project.projectStatusId) || "00000000-0000-0000-0000-000000000000", projectStatusId: String(project?.projectStatus?.id) || "00000000-0000-0000-0000-000000000000",
projectStatusId: project?.projectStatusId && project.projectStatusId !== DEFAULT_EMPTY_STATUS_ID }
? String(project.projectStatusId)
: ACTIVE_STATUS_ID,
}
: {} : {}
); );
setAddressLength(project?.projectAddress?.length || 0); setAddressLength(project?.projectAddress?.length || 0);
@ -130,9 +115,10 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
*/ */
const onSubmitForm = (updatedProject) => { const onSubmitForm = ( updatedProject ) =>
setLoading(true); {
handleSubmitForm(updatedProject, setLoading, reset);
handleSubmitForm(updatedProject);
}; };
const handleCancel = () => { const handleCancel = () => {
@ -144,27 +130,15 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
projectAddress: project?.projectAddress || "", projectAddress: project?.projectAddress || "",
startDate: formatDate(project?.startDate) || currentDate, startDate: formatDate(project?.startDate) || currentDate,
endDate: formatDate(project?.endDate) || currentDate, endDate: formatDate(project?.endDate) || currentDate,
// projectStatusId: String(project?.projectStatusId || "00000000-0000-0000-0000-000000000000"), projectStatusId: String(project?.projectStatus?.id || "00000000-0000-0000-0000-000000000000"),
projectStatusId: project?.projectStatusId && project.projectStatusId !== DEFAULT_EMPTY_STATUS_ID
? String(project.projectStatusId)
: ACTIVE_STATUS_ID,
}); });
onClose(); onClose();
}; };
return ( return (
<div
className="modal-dialog modal-lg modal-simple mx-sm-auto mx-1 edit-project-modal" <div className="p-sm-2 p-2">
role="document"
>
<div className="modal-content">
<div className="modal-body p-sm-4 p-0">
<button
type="button"
className="btn-close"
onClick={handleCancel}
aria-label="Close"
></button>
<div className="text-center mb-2"> <div className="text-center mb-2">
<h5 className="mb-2"> <h5 className="mb-2">
{project?.id ? "Edit Project" : "Create Project"} {project?.id ? "Edit Project" : "Create Project"}
@ -179,7 +153,7 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
type="text" type="text"
id="name" id="name"
name="name" name="name"
className="form-control" className="form-control form-control-sm"
placeholder="Project Name" placeholder="Project Name"
{...register("name")} {...register("name")}
/> />
@ -200,7 +174,7 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
type="text" type="text"
id="shortName" id="shortName"
name="shortName" name="shortName"
className="form-control" className="form-control form-control-sm"
placeholder="Short Name" placeholder="Short Name"
{...register("shortName")} {...register("shortName")}
/> />
@ -221,7 +195,7 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
type="text" type="text"
id="contactPerson" id="contactPerson"
name="contactPerson" name="contactPerson"
className="form-control" className="form-control form-control-sm"
placeholder="Contact Person" placeholder="Contact Person"
maxLength={50} maxLength={50}
{...register("contactPerson")} {...register("contactPerson")}
@ -283,7 +257,7 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
<select <select
id="modalEditUserStatus" id="modalEditUserStatus"
name="modalEditUserStatus" name="modalEditUserStatus"
className="select2 form-select" className="select2 form-select form-select-sm"
aria-label="Default select example" aria-label="Default select example"
{...register("projectStatusId", { {...register("projectStatusId", {
required: "Status is required", required: "Status is required",
@ -339,22 +313,21 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose }) => {
)} )}
</div> </div>
<div className="col-12 text-center"> <div className="col-12 text-center">
<button type="submit" className="btn btn-sm btn-primary me-3" disabled={isloading}> <button type="submit" className="btn btn-sm btn-primary me-3" disabled={isPending}>
{isloading ? "Please Wait..." : project?.id ? "Update" : "Submit"} {isPending ? "Please Wait..." : project?.id ? "Update" : "Submit"}
</button> </button>
<button <button
type="button" type="button"
className="btn btn-sm btn-label-secondary" className="btn btn-sm btn-label-secondary"
onClick={handleCancel} onClick={handleCancel}
aria-label="Close" aria-label="Close"
disabled={isPending}
> >
Cancel Cancel
</button> </button>
</div> </div>
</form> </form>
</div> </div>
</div>
</div>
); );
}; };

View File

@ -1,8 +1,8 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import moment from "moment"; import moment from "moment";
import { getDateDifferenceInDays } from "../../utils/dateUtils"; import { formatNumber, getDateDifferenceInDays } from "../../utils/dateUtils";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useProjectDetails } from "../../hooks/useProjects"; import { useProjectDetails, useUpdateProject } from "../../hooks/useProjects";
import ManageProjectInfo from "./ManageProjectInfo"; import ManageProjectInfo from "./ManageProjectInfo";
import ProjectRepository from "../../repositories/ProjectRepository"; import ProjectRepository from "../../repositories/ProjectRepository";
import { cacheData, getCachedData } from "../../slices/apiDataManager"; import { cacheData, getCachedData } from "../../slices/apiDataManager";
@ -13,29 +13,36 @@ import {
getProjectStatusColor, getProjectStatusColor,
getProjectStatusName, getProjectStatusName,
} from "../../utils/projectStatus"; } from "../../utils/projectStatus";
import GlobalModel from "../common/GlobalModel";
const ProjectCard = ({ projectData, recall }) => { const ProjectCard = ({ projectData, recall }) => {
const [projectInfo, setProjectInfo] = useState(projectData); const [ projectInfo, setProjectInfo ] = useState( projectData );
const [projectDetails, setProjectDetails] = useState(null); const { projects_Details, loading, error, refetch } = useProjectDetails(
projectInfo?.id,false
);
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const ManageProject = useHasUserPermission(MANAGE_PROJECT); const ManageProject = useHasUserPermission(MANAGE_PROJECT);
const [modifyProjectLoading, setMdifyProjectLoading] = useState(false); const {
mutate: updateProject,
isPending,
isSuccess,
isError,
} = useUpdateProject({
onSuccessCallback: () => {
setShowModal(false);
},
})
useEffect(()=>{ useEffect(()=>{
setProjectInfo(projectData); setProjectInfo(projectData);
},[projectData]) }, [ projectData ] )
// console.log("in card view",projectInfo);
const handleShow = async () => { const handleShow = async () => {
try { try {
setMdifyProjectLoading(true); const { data } = await refetch();
const response = await ProjectRepository.getProjectByprojectId(
projectInfo.id
);
setProjectDetails(response.data);
setMdifyProjectLoading(false);
setShowModal(true); setShowModal(true);
} catch (error) { } catch (err) {
showToast("Failed to load project details", "error"); showToast("Failed to load project details", "error");
} }
}; };
@ -54,64 +61,30 @@ const ProjectCard = ({ projectData, recall }) => {
}; };
const handleFormSubmit = (updatedProject) => { const handleFormSubmit = (updatedProject) => {
if (projectInfo?.id) { if (projectInfo?.id) {
ProjectRepository.updateProject(projectInfo.id, updatedProject) updateProject({
.then((response) => { projectId: projectInfo.id,
const updatedProjectData = { updatedData: updatedProject,
...projectInfo, });
...response.data, }
building: projectDetails?.building, };
};
setProjectInfo(updatedProject);
if (getCachedData(`projectinfo-${projectInfo.id}`)) {
cacheData(`projectinfo-${projectInfo.id}`, updatedProjectData);
}
const projects_list = getCachedData("projectslist");
if (projects_list) {
const updatedProjectsList = projects_list.map((project) =>
project.id === projectInfo.id
? {
...project,
...response.data,
// tenant: project.tenant
}
: project
);
cacheData("projectslist", updatedProjectsList);
}
recall(getCachedData("projectslist"));
showToast("Project updated successfully.", "success");
setShowModal(false);
})
.catch((error) => {
showToast(error.message, "error");
});
}
};
return ( return (
<> <>
{showModal && projectDetails && (
<div {showModal && projects_Details && (
className="modal fade show" <GlobalModel isOpen={showModal} closeModal={handleClose}>
tabIndex="-1" <ManageProjectInfo
role="dialog" project={projects_Details}
style={{ display: "block" }}
aria-hidden="false"
>
<ManageProjectInfo
project={projectDetails}
handleSubmitForm={handleFormSubmit} handleSubmitForm={handleFormSubmit}
onClose={handleClose} onClose={handleClose}
isPending={isPending}
/> />
</div> </GlobalModel>
)} )}
<div className="col-md-6 col-lg-4 col-xl-4 order-0 mb-4"> <div className="col-md-6 col-lg-4 col-xl-4 order-0 mb-4">
<div className="card cursor-pointer"> <div className={`card cursor-pointer ${isPending ? "bg-light opacity-50 pointer-events-none" : ""}`}>
<div className="card-header pb-4"> <div className="card-header pb-4">
<div className="d-flex align-items-start"> <div className="d-flex align-items-start">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
@ -143,23 +116,23 @@ const ProjectCard = ({ projectData, recall }) => {
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
aria-expanded="false" aria-expanded="false"
> >
{modifyProjectLoading ? ( {loading ? (
<div <div
className="spinner-border spinner-border-sm text-secondary" className="spinner-border spinner-border-sm text-secondary"
role="status" role="status"
> >
<span className="visually-hidden">Loading...</span> <span className="visually-hidden">Loading...</span>
</div> </div>
) : ( ) : (
<i <i
className="bx bx-dots-vertical-rounded bx-sm text-muted" className="bx bx-dots-vertical-rounded bx-sm text-muted"
data-bs-toggle="tooltip" data-bs-toggle="tooltip"
data-bs-offset="0,8" data-bs-offset="0,8"
data-bs-placement="top" data-bs-placement="top"
data-bs-custom-class="tooltip-dark" data-bs-custom-class="tooltip-dark"
title="More Action" title="More Action"
></i> ></i>
)} )}
</button> </button>
<ul className="dropdown-menu dropdown-menu-end"> <ul className="dropdown-menu dropdown-menu-end">
<li> <li>
@ -251,7 +224,7 @@ const ProjectCard = ({ projectData, recall }) => {
</div> </div>
<div className="d-flex justify-content-between align-items-center mb-2"> <div className="d-flex justify-content-between align-items-center mb-2">
<small className="text-body"> <small className="text-body">
Task: {projectInfo.completedWork} / {projectInfo.plannedWork} Task: {formatNumber(projectInfo.completedWork)} / {formatNumber(projectInfo.plannedWork)}
</small> </small>
<small className="text-body"> <small className="text-body">
{Math.floor( {Math.floor(

View File

@ -8,7 +8,6 @@ import TaskModel from "./Infrastructure/TaskModel";
import ProjectRepository, { import ProjectRepository, {
TasksRepository, TasksRepository,
} from "../../repositories/ProjectRepository"; } from "../../repositories/ProjectRepository";
import ProjectModal from "./ProjectModal";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { MANAGE_PROJECT_INFRA } from "../../utils/constants"; import { MANAGE_PROJECT_INFRA } from "../../utils/constants";
import InfraTable from "./Infrastructure/InfraTable"; import InfraTable from "./Infrastructure/InfraTable";
@ -17,334 +16,38 @@ import {
clearCacheKey, clearCacheKey,
getCachedData, getCachedData,
} from "../../slices/apiDataManager"; } from "../../slices/apiDataManager";
import { useProjectDetails } from "../../hooks/useProjects"; import { useProjectDetails, useProjectInfra } from "../../hooks/useProjects";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { refreshData } from "../../slices/localVariablesSlice"; import { refreshData } from "../../slices/localVariablesSlice";
import eventBus from "../../services/eventBus"; 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 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 { 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 ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
const [showModalFloor, setshowModalFloor] = useState(false);
const [isBuildingModalOpen, setIsBuildingModalOpen] = useState(false); const [showModalWorkArea, setshowModalWorkArea] = useState(false);
const [isFloorModalOpen, setIsFloorModalOpen] = useState(false); const [showModalTask, setshowModalTask] = useState(false);
const [isWorkAreaModelOpen, setIsWorkAreaModalOpen] = useState(false); const [showModalBuilding, setshowModalBuilding] = 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 dispatch = useDispatch(); const dispatch = useDispatch();
useEffect(() => { useEffect(() => {
setProject(projects_Details); setProject(projectInfra);
}, [data, projects_Details]); }, [data, projects_Details]);
const openFloorModel = (projectData) => { // useEffect(() => {
setIsFloorModalOpen(true); // if (reloadedData) {
}; // refetch();
const closeFloorModel = () => { // dispatch(refreshData(false));
setIsFloorModalOpen(false); // }
}; // }, [reloadedData]);
const openAssignModel = (assignData) => {
setCurrentBuilding(assignData);
setIsAssingRoleModal(true);
};
const openBuildingModel = (projectData) => {
setIsBuildingModalOpen(true);
};
const closeBuildingModel = () => {
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);
};
const closeWorkAreaModel = () => {
setIsWorkAreaModalOpen(false);
};
const handleWorkAreaModelFormSubmit = (updatedModel) => {
if (updatedModel.id == "") delete updatedModel.id;
submitData([
{
building: null,
floor: null,
workArea: updatedModel,
},
]);
};
const openTaskModel = (projectData) => {
setIsTaskModalOpen(true);
};
const closeTaskModel = () => {
setIsTaskModalOpen(false);
};
const handleTaskModelFormSubmit = (updatedModel) => {
if (updatedModel.id == "") updatedModel.id = null;
const updatedProject = { ...project };
ProjectRepository.manageProjectTasks([updatedModel])
.then((response) => {
onDataChange("task-change");
showToast("Details updated successfully.", "success");
// setClearFormTrigger( true );
if (response?.data[0]) {
const { workItemId, workItem } = response.data[0];
const updatedBuildings = updatedProject.buildings.map((building) =>
building.id == updatedModel.buildingID
? {
...building,
floors: building.floors.map((floor) =>
floor.id == updatedModel.floorId
? {
...floor,
workAreas: floor.workAreas.map((workArea) =>
workArea.id === workItem?.workAreaId
? {
...workArea,
workItems: workArea.workItems.some(
(existingItem) =>
existingItem.workItemId ===
workItem.workItemId
)
? [...workArea.workItems] // Create a new array to trigger re-render
: [...workArea.workItems, workItem],
}
: workArea
),
}
: floor
),
}
: building
);
updatedProject.buildings = updatedBuildings;
// workItem update, but having local state issue there for needed to calling api
clearCacheKey("projectInfo");
refetch();
cacheData("projectInfo", {
projectId: updatedProject.id,
data: updatedProject,
});
setProject(updatedProject);
// closeTaskModel()
}
})
.catch((error) => {
showToast(error.message, "error");
});
};
const submitData = async (infraObject) => {
try {
let response = await ProjectRepository.manageProjectInfra(infraObject);
const entity = response.data;
const updatedProject = { ...project };
// Handle the building data
if (entity.building) {
const { id, name, description } = entity.building;
const updatedBuildings = updatedProject?.buildings?.map((building) =>
building.id === id ? { ...building, name, description } : building
);
// Add building if it doesn't exist
if (!updatedProject.buildings.some((building) => building.id === id)) {
updatedBuildings.push({
id: id,
name,
description,
floors: [],
});
}
updatedProject.buildings = updatedBuildings;
// Update the cache for buildings
cacheData("projectInfo", {
projectId: updatedProject.id,
data: updatedProject,
});
setProject((prevProject) => ({
...prevProject,
buildings: updatedBuildings,
}));
// closeBuildingModel()
}
// Handle the floor data
else if (entity.floor) {
const { buildingId, id, floorName } = entity.floor;
const updatedBuildings = updatedProject?.buildings?.map((building) =>
building.id == buildingId
? {
...building,
floors: building.floors
.map((floor) =>
floor.id === id
? {
...floor,
floorName, // Update the floor name only
// Keep other properties as they are (including workArea)
}
: floor
)
// Add the new floor if it doesn't already exist
.concat(
!building.floors.some((floor) => floor.id === id)
? [{ id: id, floorName, workAreas: [] }] // New floor added with workArea set to null
: []
),
}
: building
);
updatedProject.buildings = updatedBuildings;
// Cache the updated project
cacheData("projectInfo", {
projectId: updatedProject.id,
data: updatedProject,
});
setProject(updatedProject);
// closeFloorModel()
}
// Handle the work area data
else if (entity.workArea) {
let buildingId = infraObject[0].workArea.buildingId;
const { floorId, areaName, id } = entity.workArea;
// Check if the workArea exists, otherwise create a new one
const updatedBuildings = updatedProject.buildings.map((building) =>
building.id == buildingId
? {
...building,
floors: building.floors.map((floor) =>
floor.id == floorId
? {
...floor,
workAreas: floor.workAreas.some(
(workArea) => workArea.id === id
)
? floor.workAreas.map((workArea) =>
workArea.id === id
? { ...workArea, areaName }
: workArea
)
: [
...floor.workAreas,
{ id, areaName, workItems: [] },
],
}
: floor
),
}
: building
);
updatedProject.buildings = updatedBuildings;
// Update the cache for work areas
cacheData("projectInfo", {
projectId: updatedProject.id,
data: updatedProject,
});
setProject(updatedProject);
// closeWorkAreaModel()
}
// Handle the task (workItem) data
else {
console.error("Unsupported data type for submitData", entity);
}
} catch (Err) {
showToast("Somthing wrong", "error");
}
};
const toggleBuilding = (id) => {
setExpandedBuildings((prev) =>
prev.includes(id) ? prev.filter((bid) => bid !== id) : [...prev, id]
);
};
const handleModalData = (type, modaldata) => {
setModalConfig({ type: type, data: modaldata });
};
const openModal = () => {
const modalElement = document.getElementById("building-model");
const modal = new Modal(modalElement, {
backdrop: false,
keyboard: true,
focus: true,
});
modal.show();
};
const closeModal = () => {
setIsModalOpen(false);
setModalConfig(null);
const modalElement = document.getElementById("building-model");
if (modalElement) {
modalElement.classList.remove("show"); // Remove modal visibility class
modalElement.style.display = "none"; // Hide the modal element
}
document.body.classList.remove("modal-open"); // Remove modal-open class from body
// 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
}
document.body.style.overflow = "auto";
};
const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal(false);
useEffect(() => {
if (reloadedData) {
refetch();
dispatch(refreshData(false));
}
}, [reloadedData]);
const signalRHandler = (response) => { const signalRHandler = (response) => {
setProject(response); setProject(response);
@ -352,82 +55,30 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
return ( return (
<> <>
<div {showModalBuilding && <GlobalModel isOpen={showModalBuilding} size="md" closeModal={() => setshowModalBuilding( false )}>
className={`modal fade ${showModal ? "show" : ""}`}
tabIndex="-1"
role="dialog"
style={{ display: showModal ? "block" : "none" }}
aria-hidden={!showModal}
>
<BuildingModel <BuildingModel
project={project} project={projectInfra}
onClose={handleClose} onClose={() => setshowModalBuilding( false )}
onSubmit={handleBuildingModelFormSubmit} />
clearTrigger={clearFormTrigger} </GlobalModel>}
onClearComplete={() => setClearFormTrigger(false)} {showModalFloor && <GlobalModel isOpen={showModalFloor} size="md" closeModal={()=>setshowModalFloor(false)}>
></BuildingModel> <FloorModel
</div> project={projectInfra}
{isFloorModalOpen && ( onClose={()=>setshowModalFloor(false)}
<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)}
/> />
</div> </GlobalModel>}
)} {showModalWorkArea && <GlobalModel isOpen={showModalWorkArea} size="lg" closeModal={()=>setshowModalWorkArea(false)} >
{isWorkAreaModelOpen && (
<div
className="modal fade show"
id="work-area-model"
tabIndex="-1"
role="dialog"
style={{ display: "block" }}
aria-hidden="false"
>
<WorkAreaModel <WorkAreaModel
project={project} project={projectInfra}
onClose={closeWorkAreaModel} onClose={()=>setshowModalWorkArea(false)}
onSubmit={handleWorkAreaModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
/> />
</div> </GlobalModel>}
)} {showModalTask && ( <GlobalModel isOpen={showModalTask} size="lg" closeModal={()=>setshowModalTask(false)}>
<TaskModel
{isTaskModelOpen && ( project={projectInfra}
<div onClose={()=>setshowModalTask(false)}
className="modal fade show"
id="task-model"
tabIndex="-1"
role="dialog"
style={{ display: "block" }}
aria-hidden="false"
>
<TaskModel
project={project}
onClose={closeTaskModel}
onSubmit={handleTaskModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
/> />
</div> </GlobalModel>)}
)}
{isModalOpen && (
<ProjectModal modalConfig={modalConfig} closeModal={closeModal} />
)}
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4"> <div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
<div className="card"> <div className="card">
<div className="card-body" style={{ padding: "0.5rem" }}> <div className="card-body" style={{ padding: "0.5rem" }}>
@ -441,15 +92,15 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
<button <button
type="button" type="button"
className="link-button link-button-sm m-1 " className="link-button link-button-sm m-1 "
onClick={handleShow} onClick={()=>setshowModalBuilding(true)}
> >
<i className="bx bx-plus-circle me-2"></i> <i className="bx bx-plus-circle me-2"></i>
Manage Building Manage Building
</button> </button>
<button <button
type="button" type="button"
className="link-button m-1" className="link-button m-1"
onClick={() => openFloorModel()} onClick={()=>setshowModalFloor(true)}
> >
<i className="bx bx-plus-circle me-2"></i> <i className="bx bx-plus-circle me-2"></i>
Manage Floors Manage Floors
@ -457,7 +108,7 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
<button <button
type="button" type="button"
className="link-button m-1" className="link-button m-1"
onClick={() => openWorkAreaModel()} onClick={() => setshowModalWorkArea(true)}
> >
<i className="bx bx-plus-circle me-2"></i> <i className="bx bx-plus-circle me-2"></i>
Manage Work Areas Manage Work Areas
@ -465,7 +116,7 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
<button <button
type="button" type="button"
className="link-button m-1" className="link-button m-1"
onClick={() => openTaskModel()} onClick={()=>setshowModalTask(true)}
> >
<i className="bx bx-plus-circle me-2"></i> <i className="bx bx-plus-circle me-2"></i>
Create Tasks Create Tasks
@ -473,15 +124,16 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
</div> </div>
</div> </div>
<div className="row "> <div className="row ">
{loading && <p>Loading....</p>} {isLoading && <p>Loading....</p>}
{project && project.buildings?.length > 0 && ( {projectInfra && projectInfra?.length > 0 && (
<InfraTable <InfraTable
buildings={project?.buildings} buildings={projectInfra}
projectId={project.id} projectId={projectId}
handleFloor={submitData} // handleFloor={submitData}
signalRHandler = {signalRHandler} // signalRHandler ={signalRHandler}
/> />
)} )}
{!isLoading && projectInfra?.length == 0 && <div className="mt-5"><p>No Infra Avaiable</p></div>}
</div> </div>
</div> </div>
</div> </div>

View File

@ -23,7 +23,6 @@ const ProjectModal = ({modalConfig,closeModal}) => {
></button> ></button>
<div className="text-center mb-2"></div> <div className="text-center mb-2"></div>
{/* Modal Component */}
{modalConfig?.type === "assignRole" && <AssignRole assignData={modalConfig?.data} onClose={closeModal} />} {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" : ""}`} className={`nav-link ${activePill === "profile" ? "active" : ""}`}
href="#" href="#"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); // Prevent page reload e.preventDefault();
onPillClick("profile"); onPillClick("profile");
}} }}
> >
@ -29,7 +29,7 @@ const ProjectNav = ({ onPillClick, activePill }) => {
className={`nav-link ${activePill === "teams" ? "active" : ""}`} className={`nav-link ${activePill === "teams" ? "active" : ""}`}
href="#" href="#"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); // Prevent page reload e.preventDefault();
onPillClick("teams"); onPillClick("teams");
}} }}
> >
@ -41,27 +41,14 @@ const ProjectNav = ({ onPillClick, activePill }) => {
className={`nav-link ${activePill === "infra" ? "active" : ""}`} className={`nav-link ${activePill === "infra" ? "active" : ""}`}
href="#" href="#"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); // Prevent page reload e.preventDefault();
onPillClick("infra"); onPillClick("infra");
}} }}
> >
<i className="bx bx-grid-alt bx-sm me-1_5"></i> <span className="d-none d-md-inline">Infrastructure</span> <i className="bx bx-grid-alt bx-sm me-1_5"></i> <span className="d-none d-md-inline">Infrastructure</span>
</a> </a>
</li> </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"> <li className="nav-item">
<a <a
className={`nav-link ${ className={`nav-link ${

View File

@ -3,61 +3,14 @@ import {
useEmployeesByProjectAllocated, useEmployeesByProjectAllocated,
useProjects, useProjects,
} from "../../hooks/useProjects"; } from "../../hooks/useProjects";
import { formatNumber } from "../../utils/dateUtils";
import ProgressBar from "../common/ProgressBar";
const ProjectOverview = ({ project }) => { const ProjectOverview = ({ project }) => {
const { projects } = useProjects(); const { projects } = useProjects();
const getProgress = (planned, completed) => {
return (completed * 100) / planned + "%";
};
const getProgressInNumber = (planned, completed) => {
var number = (completed * 100) / planned;
return FormattedNumber(number);
};
const project_detail = projects.find((pro) => pro.id == project); const project_detail = projects.find((pro) => pro.id == project);
// Utility function to check if a number has a decimal part
const hasDecimal = (num) => {
// Convert to string and check for a decimal point
// Or, check if the number is not equal to its integer part
return num % 1 !== 0;
};
// FormattedNumber component to display numbers with conditional decimal places
function FormattedNumber(value, locale = "en-US") {
// Ensure the value is a number
const numericValue = parseFloat(value);
// Handle non-numeric values gracefully
if (isNaN(numericValue)) {
return <span>Invalid Number</span>;
}
let options = {};
// Determine formatting options based on whether the number has a decimal part
if (hasDecimal(numericValue)) {
// If it has a decimal, format to exactly two decimal places
options = {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
};
} else {
// If it's a whole number, format to zero decimal places
options = {
minimumFractionDigits: 0,
maximumFractionDigits: 0,
};
}
// Use Intl.NumberFormat for robust and locale-aware formatting
const formattedString = new Intl.NumberFormat(locale, options).format(
numericValue
);
return formattedString;
}
return ( return (
<div className="card mb-6"> <div className="card mb-6">
<div className="card-header text-start"> <div className="card-header text-start">
@ -72,12 +25,12 @@ const ProjectOverview = ({ project }) => {
<li className="d-flex align-items-center mb-3"> <li className="d-flex align-items-center mb-3">
<i className="bx bx-check"></i> <i className="bx bx-check"></i>
<span className="fw-medium mx-2">Task Planned:</span>{" "} <span className="fw-medium mx-2">Task Planned:</span>{" "}
<span>{project_detail?.plannedWork}</span> <span>{formatNumber(project_detail?.plannedWork)}</span>
</li> </li>
<li className="d-flex align-items-center mb-3"> <li className="d-flex align-items-center mb-3">
<i className="bx bx-star"></i> <i className="bx bx-star"></i>
<span className="fw-medium mx-2">Task Completed:</span>{" "} <span className="fw-medium mx-2">Task Completed:</span>{" "}
<span>{project_detail?.completedWork}</span> <span>{formatNumber(project_detail?.completedWork)}</span>
</li> </li>
<li className="d-flex align-items-center mb-3"> <li className="d-flex align-items-center mb-3">
<i className="bx bx-user"></i> <i className="bx bx-user"></i>
@ -89,16 +42,21 @@ const ProjectOverview = ({ project }) => {
<> <>
<div className="d-flex text-end mb-2 mt-5"> <div className="d-flex text-end mb-2 mt-5">
<small className="text-body text-muted "> <small className="text-body text-muted ">
{Math.floor( {/* {Math.floor(
getProgressInNumber( getProgressInNumber(
project_detail.plannedWork,
project_detail.completedWork project_detail.completedWork
) )
) || 0}{" "} ) || 0}{" "} */}
{
(formatNumber(project_detail.plannedWork),
"/",
formatNumber(project_detail.completedWork))
}
% Completed % Completed
</small> </small>
</div> </div>
<div {/* <div
className="progress mb-4 rounded" className="progress mb-4 rounded"
style={{ height: "8px" }} style={{ height: "8px" }}
> >
@ -115,7 +73,12 @@ const ProjectOverview = ({ project }) => {
aria-valuemin="0" aria-valuemin="0"
aria-valuemax={project_detail.plannedWork} aria-valuemax={project_detail.plannedWork}
></div> ></div>
</div> </div> */}
<ProgressBar
completedWork={formatNumber(project_detail?.completedWork)}
plannedWork={formatNumber(project_detail?.plannedWork)}
className="m-0 text-info"
/>
</> </>
)} )}
</li> </li>

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback } from "react";
import MapUsers from "./MapUsers"; 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 showToast from "../../services/toastService";
import Avatar from "../common/Avatar"; import Avatar from "../common/Avatar";
@ -14,8 +14,11 @@ import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { ASSIGN_TO_PROJECT } from "../../utils/constants"; import { ASSIGN_TO_PROJECT } from "../../utils/constants";
import ConfirmModal from "../common/ConfirmModal"; import ConfirmModal from "../common/ConfirmModal";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import {useEmployeesByProjectAllocated, useManageProjectAllocation} from "../../hooks/useProjects";
const Teams = ({ project }) => { const Teams = () =>
{
const {projectId} = useParams()
const dispatch = useDispatch(); const dispatch = useDispatch();
const { data, loading } = useMaster(); const { data, loading } = useMaster();
@ -26,87 +29,64 @@ const Teams = ({ project }) => {
const [filteredEmployees, setFilteredEmployees] = useState([]); const [filteredEmployees, setFilteredEmployees] = useState([]);
const [removingEmployeeId, setRemovingEmployeeId] = useState(null); const [removingEmployeeId, setRemovingEmployeeId] = useState(null);
const [assignedLoading, setAssignedLoading] = useState(false); const [assignedLoading, setAssignedLoading] = useState(false);
const [ employeeLodaing, setEmployeeLoading ] = useState( false );
const [ activeEmployee, setActiveEmployee ] = useState( true ) const [ activeEmployee, setActiveEmployee ] = useState( true )
const [deleteEmployee,setDeleteEmplyee] = useState(null) const [deleteEmployee,setDeleteEmplyee] = useState(null)
const navigate = useNavigate(); const navigate = useNavigate();
const HasAssignUserPermission = useHasUserPermission( ASSIGN_TO_PROJECT ); const HasAssignUserPermission = useHasUserPermission( ASSIGN_TO_PROJECT );
const[IsDeleteModal,setIsDeleteModal] = useState(false) const [ IsDeleteModal, setIsDeleteModal ] = useState( false )
const {projectEmployees, loading:employeeLodaing, refetch} = useEmployeesByProjectAllocated( projectId )
const {
mutate: submitAllocations,
isPending,
isSuccess,
isError,
} = useManageProjectAllocation({
onSuccessCallback: () => {
setRemovingEmployeeId(null);
setAssignedLoading(false);
setDeleteEmplyee(null);
closeDeleteModal();
},
onErrorCallback: () => {
closeDeleteModal();
},
});
const fetchEmployees = async () => {
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) => {
setError("Failed to fetch data.");
setEmployeeLoading(false);
});
} catch (err) {
setError("Failed to fetch activities.");
}
};
const submitAllocations = (items,added) => {
ProjectRepository.manageProjectAllocation(items)
.then((response) => {
fetchEmployees();
if ( added )
{
showToast("Employee Assigned Successfully", "success");
}else{
showToast("Removed Employee Successfully", "success");
}
setRemovingEmployeeId(null);
setAssignedLoading( false );
setDeleteEmplyee( null )
closeDeleteModal()
})
.catch((error) => {
const message = error.response.data.message || error.message || "Error Occured during Api Call";
showToast( message, "error" );
closeDeleteModal()
});
};
const removeAllocation = (item) => { const removeAllocation = (item) => {
setRemovingEmployeeId(item.id); setRemovingEmployeeId(item.id);
submitAllocations([
submitAllocations({
items: [
{ {
empID: item.employeeId, empID: item.employeeId,
jobRoleId: item.jobRoleId, jobRoleId: item.jobRoleId,
projectId: project.id, projectId: projectId,
status: false, status: false,
}, },
] ,false); ],
added: false,
}; });
};
const handleEmpAlicationFormSubmit = (allocaionObj) => { const handleEmpAlicationFormSubmit = (allocaionObj) => {
let items = allocaionObj.map((item) => { let items = allocaionObj.map((item) => {
return { return {
empID: item.empID, empID: item.empID,
jobRoleId: item.jobRoleId, jobRoleId: item.jobRoleId,
projectId: project.id, projectId: projectId,
status: true, status: true,
}; };
}); });
submitAllocations(items, true); submitAllocations({ items, added: true });
// Force switch to active view after assignment
setActiveEmployee(true); setActiveEmployee(true);
setFilteredEmployees(employees.filter((emp) => emp.isActive)); setFilteredEmployees(employees.filter((emp) => emp.isActive));
// Also update dropdown select if needed
const dropdown = document.querySelector('select[name="DataTables_Table_0_length"]'); const dropdown = document.querySelector('select[name="DataTables_Table_0_length"]');
if (dropdown) dropdown.value = "true"; if (dropdown) dropdown.value = "true";
}; };
@ -146,8 +126,12 @@ const Teams = ({ project }) => {
useEffect(() => { useEffect(() => {
fetchEmployees(); if ( projectEmployees )
}, []); {
setEmployees(projectEmployees);
setFilteredEmployees( projectEmployees?.filter( ( emp ) => emp.isActive ) );
}
}, [projectEmployees,employeeLodaing]);
useEffect(() => { useEffect(() => {
if (data) { if (data) {
@ -176,11 +160,11 @@ const Teams = ({ project }) => {
const handler = useCallback( const handler = useCallback(
(msg) => { (msg) => {
if (msg.projectIds.some((item) => item === project.id)) { if (msg.projectIds.some((item) => item === projectId)) {
fetchEmployees(); refetch();
} }
}, },
[] [projectId, refetch]
); );
useEffect(() => { useEffect(() => {
@ -191,9 +175,9 @@ const Teams = ({ project }) => {
const employeeHandler = useCallback( const employeeHandler = useCallback(
(msg) => { (msg) => {
if(filteredEmployees.some((item) => item.employeeId == msg.employeeId)){ if(filteredEmployees.some((item) => item.employeeId == msg.employeeId)){
fetchEmployees(); refetch();
} }
},[filteredEmployees] },[filteredEmployees, refetch]
); );
useEffect(() => { useEffect(() => {
@ -211,7 +195,7 @@ const Teams = ({ project }) => {
aria-hidden="true" aria-hidden="true"
> >
<MapUsers <MapUsers
projectId={project?.id} projectId={projectId}
onClose={onModelClose} onClose={onModelClose}
empJobRoles={empJobRoles} empJobRoles={empJobRoles}
onSubmit={handleEmpAlicationFormSubmit} onSubmit={handleEmpAlicationFormSubmit}
@ -240,7 +224,7 @@ const Teams = ({ project }) => {
message={"Are you sure you want delete?"} message={"Are you sure you want delete?"}
onSubmit={removeAllocation} onSubmit={removeAllocation}
onClose={closeDeleteModal} onClose={closeDeleteModal}
loading={employeeLodaing} loading={isPending}
paramData={deleteEmployee} paramData={deleteEmployee}
/> />
</div> </div>

View File

@ -48,9 +48,8 @@
} }
.img-preview img { .img-preview img {
max-width: 100%; width: 100%;
max-height: 80vh; height: auto;
object-fit: contain; aspect-ratio: 16 / 10;
display: block; object-fit: cover;
margin: 0 auto; }
}

View File

@ -7,6 +7,7 @@ import { MasterRespository } from "../../repositories/MastersRepository";
import { clearApiCacheKey } from "../../slices/apiCacheSlice"; import { clearApiCacheKey } from "../../slices/apiCacheSlice";
import { getCachedData, cacheData } from "../../slices/apiDataManager"; import { getCachedData, cacheData } from "../../slices/apiDataManager";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import {useCreateActivity} from "../../hooks/masterHook/useMaster";
const schema = z.object({ const schema = z.object({
activityName: z.string().min(1, { message: "Activity Name is required" }), activityName: z.string().min(1, { message: "Activity Name is required" }),
@ -23,101 +24,96 @@ const schema = z.object({
}); });
const CreateActivity = ({ onClose }) => { const CreateActivity = ({ onClose }) => {
const [isLoading, setIsLoading] = useState(false); const maxDescriptionLength = 255;
const { mutate: createActivity, isPending: isLoading } = useCreateActivity(() => onClose?.());
const { const {
register, register,
handleSubmit, handleSubmit,
control, control,
setValue, setValue,
clearErrors, clearErrors,
setError, setError,
getValues, getValues,
reset, reset,
formState: { errors }, formState: { errors },
} = useForm({ } = useForm({
resolver: zodResolver(schema), resolver: zodResolver(schema),
defaultValues: { defaultValues: {
activityName: "", activityName: "",
unitOfMeasurement: "", unitOfMeasurement: "",
checkList: [], checkList: [],
}, },
}); });
const {
fields: checkListItems,
append,
remove,
} = useFieldArray({
control,
name: "checkList",
});
const { const addChecklistItem = useCallback(() => {
fields: checkListItems, const values = getValues("checkList");
append, const lastIndex = checkListItems.length - 1;
remove,
} = useFieldArray({
control,
name: "checkList",
});
// Form submission handler if (
const onSubmit = (data) => { checkListItems.length > 0 &&
setIsLoading(true); (!values?.[lastIndex] || values[lastIndex].description.trim() === "")
) {
MasterRespository.createActivity(data) setError(`checkList.${lastIndex}.description`, {
.then( ( resp ) => type: "manual",
{ message: "Please fill this checklist item before adding another.",
const cachedData = getCachedData("Activity");
const updatedData = [ ...cachedData, resp?.data ];
cacheData("Activity", updatedData);
showToast("Activity Successfully Added.", "success");
setIsLoading(false);
handleClose()
})
.catch((error) => {
showToast(error.message, "error");
setIsLoading(false);
});
};
const addChecklistItem = () => {
const values = getValues("checkList");
const lastIndex = checkListItems.length - 1;
if (
checkListItems.length > 0 &&
(!values?.[lastIndex] || values[lastIndex].description.trim() === "")
) {
setError(`checkList.${lastIndex}.description`, {
type: "manual",
message: "Please fill this checklist item before adding another.",
});
return;
}
clearErrors(`checkList.${lastIndex}.description`);
append({
id: null,
description: "",
isMandatory: false,
}); });
}; return;
}
const removeChecklistItem = (index) => { clearErrors(`checkList.${lastIndex}.description`);
remove(index); append({ id: null, description: "", isMandatory: false });
}; }, [checkListItems, getValues, append, setError, clearErrors]);
const removeChecklistItem = useCallback((index) => {
remove(index);
}, [remove]);
const handleChecklistChange = (index, value) => { const handleChecklistChange = useCallback((index, value) => {
setValue(`checkList.${index}`, value); setValue(`checkList.${index}`, value);
}; }, [setValue]);
const handleClose = () => { const onSubmit = (formData) => {
reset(); createActivity(formData);
onClose(); };
}; // const onSubmit = (data) => {
// setIsLoading(true);
// MasterRespository.createActivity(data)
// .then( ( resp ) =>
// {
// const cachedData = getCachedData("Activity");
// const updatedData = [ ...cachedData, resp?.data ];
// cacheData("Activity", updatedData);
// showToast("Activity Successfully Added.", "success");
// setIsLoading(false);
// handleClose()
// })
// .catch((error) => {
// showToast(error.message, "error");
// setIsLoading(false);
// });
// };
const handleClose = useCallback(() => {
reset();
onClose();
}, [reset, onClose]);
useEffect(() => {
const tooltipTriggerList = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el));
}, []);
// for tooltip
useEffect(() => {
const tooltipTriggerList = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el));
}, []);
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
{/* <h6>Create Activity</h6> */} {/* <h6>Create Activity</h6> */}

View File

@ -6,6 +6,7 @@ import { MasterRespository } from '../../repositories/MastersRepository';
import { clearApiCacheKey } from '../../slices/apiCacheSlice'; import { clearApiCacheKey } from '../../slices/apiCacheSlice';
import { getCachedData,cacheData } from '../../slices/apiDataManager'; import { getCachedData,cacheData } from '../../slices/apiDataManager';
import showToast from '../../services/toastService'; import showToast from '../../services/toastService';
import {useCreateContactCategory} from '../../hooks/masterHook/useMaster';
const schema = z.object({ const schema = z.object({
@ -16,51 +17,52 @@ const schema = z.object({
const CreateContactCategory = ({onClose}) => { const CreateContactCategory = ({onClose}) => {
const[isLoading,setIsLoading] = useState(false) const {
const { register,
register, handleSubmit,
handleSubmit, formState: { errors },
formState: { errors },reset reset,
} = useForm({
} = useForm({ resolver: zodResolver(schema),
resolver: zodResolver(schema), defaultValues: {
defaultValues: { name: "",
name: "", description: "",
description: "", },
});
},
});
const onSubmit = (data) => {
setIsLoading(true)
MasterRespository.createContactCategory(data).then((resp)=>{
setIsLoading(false)
resetForm()
const cachedData = getCachedData("Contact Category");
const updatedData = [...cachedData, resp?.data];
cacheData("Contact Category", updatedData);
showToast("Contact Category Added successfully.", "success");
onClose() const [descriptionLength, setDescriptionLength] = useState(0);
}).catch((error)=>{ const maxDescriptionLength = 255;
showToast(error?.response?.data?.message, "error");
setIsLoading(false)
})
};
const resetForm = () => {
reset({
name: "",
description: ""
});
setDescriptionLength(0);
}
useEffect(()=>{ const { mutate: createContactCategory, isPending: isLoading } = useCreateContactCategory(() => onClose?.());
return ()=>resetForm()
},[]) const onSubmit = (payload) => {
createContactCategory(payload);
const [descriptionLength, setDescriptionLength] = useState(0); };
const maxDescriptionLength = 255; // const onSubmit = (data) => {
// setIsLoading(true)
// MasterRespository.createContactCategory(data).then((resp)=>{
// setIsLoading(false)
// resetForm()
// const cachedData = getCachedData("Contact Category");
// const updatedData = [...cachedData, resp?.data];
// cacheData("Contact Category", updatedData);
// showToast("Contact Category Added successfully.", "success");
// onClose()
// }).catch((error)=>{
// showToast(error?.response?.data?.message, "error");
// setIsLoading(false)
// })
// };
const resetForm = () => {
reset({ name: "", description: "" });
setDescriptionLength(0);
};
useEffect(() => {
return () => resetForm();
}, []);
return (<> return (<>
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}> <form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
<div className="col-12 col-md-12"> <div className="col-12 col-md-12">

View File

@ -6,6 +6,7 @@ import { MasterRespository } from '../../repositories/MastersRepository';
import { clearApiCacheKey } from '../../slices/apiCacheSlice'; import { clearApiCacheKey } from '../../slices/apiCacheSlice';
import { getCachedData,cacheData } from '../../slices/apiDataManager'; import { getCachedData,cacheData } from '../../slices/apiDataManager';
import showToast from '../../services/toastService'; import showToast from '../../services/toastService';
import {useCreateContactTag} from '../../hooks/masterHook/useMaster';
const schema = z.object({ const schema = z.object({
@ -15,53 +16,53 @@ const schema = z.object({
}); });
const CreateContactTag = ({onClose}) => { const CreateContactTag = ({onClose}) => {
const {
register,
handleSubmit,
formState: { errors },
reset,
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
name: "",
description: "",
},
});
const[isLoading,setIsLoading] = useState(false) const [descriptionLength, setDescriptionLength] = useState(0);
const { const maxDescriptionLength = 255;
register,
handleSubmit,
formState: { errors },reset
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
name: "",
description: "",
},
});
const onSubmit = (data) => {
setIsLoading(true)
MasterRespository.createContactTag(data).then((resp)=>{
setIsLoading(false)
resetForm()
debugger
const cachedData = getCachedData("Contact Tag");
const updatedData = [...cachedData, resp?.data];
cacheData("Contact Tag", updatedData);
showToast("Contact Tag Added successfully.", "success");
console.log(getCachedData("Contact Tag"))
onClose()
}).catch((error)=>{
showToast(error?.response?.data?.message, "error");
setIsLoading(false)
})
};
const resetForm = () => {
reset({
name: "",
description: ""
});
setDescriptionLength(0);
}
useEffect(()=>{ const { mutate: createContactTag, isPending: isLoading } = useCreateContactTag(() => onClose?.());
return ()=>resetForm()
},[]) const onSubmit = (payload) => {
createContactTag(payload);
const [descriptionLength, setDescriptionLength] = useState(0); };
const maxDescriptionLength = 255; // const onSubmit = (data) => {
// setIsLoading(true)
// MasterRespository.createContactTag(data).then((resp)=>{
// setIsLoading(false)
// resetForm()
// debugger
// const cachedData = getCachedData("Contact Tag");
// const updatedData = [...cachedData, resp?.data];
// cacheData("Contact Tag", updatedData);
// showToast("Contact Tag Added successfully.", "success");
// console.log(getCachedData("Contact Tag"))
// onClose()
// }).catch((error)=>{
// showToast(error?.response?.data?.message, "error");
// setIsLoading(false)
// })
// };
const resetForm = () => {
reset({ name: "", description: "" });
setDescriptionLength(0);
};
useEffect(() => {
return () => resetForm();
}, []);
return (<> return (<>
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}> <form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
<div className="col-12 col-md-12"> <div className="col-12 col-md-12">

View File

@ -6,6 +6,7 @@ import { MasterRespository } from '../../repositories/MastersRepository';
import { clearApiCacheKey } from '../../slices/apiCacheSlice'; import { clearApiCacheKey } from '../../slices/apiCacheSlice';
import { getCachedData,cacheData } from '../../slices/apiDataManager'; import { getCachedData,cacheData } from '../../slices/apiDataManager';
import showToast from '../../services/toastService'; import showToast from '../../services/toastService';
import {useCreateJobRole} from '../../hooks/masterHook/useMaster';
const schema = z.object({ const schema = z.object({
@ -16,58 +17,75 @@ const schema = z.object({
const CreateJobRole = ({onClose}) => { const CreateJobRole = ({onClose}) => {
const[isLoading,setIsLoading] = useState(false) const maxDescriptionLength = 255;
const { const [descriptionLength, setDescriptionLength] = useState(0);
register,
handleSubmit,
formState: { errors },reset
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
role: "",
description: "",
},
});
const onSubmit = (data) => {
setIsLoading(true)
const result = {
name: data.role,
description: data.description,
};
MasterRespository.createJobRole(result).then((resp)=>{
setIsLoading(false)
resetForm()
const cachedData = getCachedData("Job Role");
const updatedData = [...cachedData, resp?.data];
cacheData("Job Role", updatedData);
showToast("JobRole Added successfully.", "success");
onClose() const {
}).catch((error)=>{ register,
showToast(error.message, "error"); handleSubmit,
setIsLoading(false) formState: { errors },
}) reset,
watch,
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
role: "",
description: "",
},
});
const resetForm = () => {
reset({ role: "", description: "" });
setDescriptionLength(0);
};
const { mutate: createJobRole, isPending:isLoading } = useCreateJobRole(() => {
onClose?.();
} );
// const onSubmit = (data) => {
// setIsLoading(true)
// const result = {
// name: data.role,
// description: data.description,
// };
// MasterRespository.createJobRole(result).then((resp)=>{
// setIsLoading(false)
// resetForm()
// const cachedData = getCachedData("Job Role");
// const updatedData = [...cachedData, resp?.data];
// cacheData("Job Role", updatedData);
// showToast("JobRole Added successfully.", "success");
// onClose()
// }).catch((error)=>{
// showToast(error.message, "error");
// setIsLoading(false)
// })
// };
const onSubmit = (data) => {
const payload = {
name: data.role,
description: data.description,
}; };
const resetForm = () => { createJobRole(payload);
reset({ };
role: "",
description: "" useEffect(() => {
}); const sub = watch((values) => {
setDescriptionLength(0); setDescriptionLength(values.description?.length || 0);
} });
return () => sub.unsubscribe();
}, [watch]);
useEffect(() => {
return () => resetForm();
}, []);
useEffect(()=>{
return ()=>resetForm()
},[])
const [descriptionLength, setDescriptionLength] = useState(0);
const maxDescriptionLength = 255;
return (<> return (<>
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}> <form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
{/* <div className="col-12 col-md-12"> {/* <div className="col-12 col-md-12">

View File

@ -1,21 +1,19 @@
import React, { useEffect, useState, useRef } from "react"; import React, { useEffect, useState, useRef } from "react";
import { useFeatures } from "../../hooks/useMasterRole"; import { useFeatures } from "../../hooks/useMasterRole";
import { useForm } from 'react-hook-form'; import { useForm } from "react-hook-form";
import { set, z } from 'zod'; import { set, z } from "zod";
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from "@hookform/resolvers/zod";
import { MasterRespository } from "../../repositories/MastersRepository"; import { MasterRespository } from "../../repositories/MastersRepository";
import { cacheData, getCachedData } from "../../slices/apiDataManager"; import { cacheData, getCachedData } from "../../slices/apiDataManager";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import { useCreateApplicationRole } from "../../hooks/masterHook/useMaster";
const schema = z.object({ const schema = z.object({
role: z.string().min(1, { message: "Role is required" }), role: z.string().min(1, { message: "Role is required" }),
description: z.string().min(1, { message: "Description is required" }) description: z
.string()
.min(1, { message: "Description is required" })
.max(255, { message: "Description cannot exceed 255 characters" }), .max(255, { message: "Description cannot exceed 255 characters" }),
selectedPermissions: z selectedPermissions: z
@ -23,21 +21,22 @@ const schema = z.object({
.min(1, { message: "Please select at least one permission" }), .min(1, { message: "Please select at least one permission" }),
}); });
const CreateRole = ({ modalType, onClose }) => { const CreateRole = ({ modalType, onClose }) => {
const maxDescriptionLength = 255;
const [isLoading, setIsLoading] = useState(false) const [descriptionLength, setDescriptionLength] = useState(0);
const popoverRefs = useRef([]); const popoverRefs = useRef([]);
const { masterFeatures } = useFeatures() const { masterFeatures } = useFeatures();
const { mutate: submitNewApplicationRole, isPending: isLoading } =
useCreateApplicationRole(() => {
onClose?.();
});
const { const {
register, register,
handleSubmit, handleSubmit,
formState: { errors }, formState: { errors },
reset,
} = useForm({ } = useForm({
resolver: zodResolver(schema), resolver: zodResolver(schema),
defaultValues: { defaultValues: {
@ -48,9 +47,7 @@ const CreateRole = ({ modalType, onClose }) => {
}); });
const onSubmit = (values) => { const onSubmit = (values) => {
setIsLoading(true) const payload = {
const result = {
role: values.role, role: values.role,
description: values.description, description: values.description,
featuresPermission: values.selectedPermissions.map((id) => ({ featuresPermission: values.selectedPermissions.map((id) => ({
@ -59,27 +56,40 @@ const CreateRole = ({ modalType, onClose }) => {
})), })),
}; };
MasterRespository.createRole(result).then((resp) => { submitNewApplicationRole(payload);
setIsLoading(false)
const cachedData = getCachedData("Application Role");
const updatedData = [...cachedData, resp.data];
cacheData("Application Role", updatedData);
showToast("Application Role Added successfully.", "success");
onClose()
}).catch((err) => {
showToast(err?.response?.data?.message, "error");
setIsLoading(false)
onClose()
})
}; };
const [descriptionLength, setDescriptionLength] = useState(0);
const maxDescriptionLength = 255; // const onSubmit = (values) => {
// setIsLoading(true)
// const result = {
// role: values.role,
// description: values.description,
// featuresPermission: values.selectedPermissions.map((id) => ({
// id,
// isEnabled: true,
// })),
// };
// MasterRespository.createRole(result).then((resp) => {
// setIsLoading(false)
// const cachedData = getCachedData("Application Role");
// const updatedData = [...cachedData, resp.data];
// cacheData("Application Role", updatedData);
// showToast("Application Role Added successfully.", "success");
// onClose()
// }).catch((err) => {
// showToast(err?.response?.data?.message, "error");
// setIsLoading(false)
// onClose()
// })
// };
useEffect(() => { useEffect(() => {
setDescriptionLength(0); setDescriptionLength(0);
}, []); }, []);
useEffect(() => { useEffect(() => {
popoverRefs.current.forEach((el) => { popoverRefs.current.forEach((el) => {
if (el) { if (el) {
@ -87,14 +97,13 @@ const CreateRole = ({ modalType, onClose }) => {
trigger: "focus", trigger: "focus",
placement: "right", placement: "right",
html: true, html: true,
content: el.getAttribute("data-bs-content"), // use inline content from attribute content: el.getAttribute("data-bs-content"),
}); });
} }
}); });
}, [masterFeatures]); }, [masterFeatures]);
return ( return (
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}> <form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
{/* <div className="col-12 col-md-12"> {/* <div className="col-12 col-md-12">
<label className="fs-5 text-dark text-center d-flex align-items-center justify-content-center flex-wrap">Create Application Role</label> <label className="fs-5 text-dark text-center d-flex align-items-center justify-content-center flex-wrap">Create Application Role</label>
@ -102,23 +111,24 @@ const CreateRole = ({ modalType, onClose }) => {
</div> */} </div> */}
<div className="col-12 col-md-12"> <div className="col-12 col-md-12">
<label className="form-label">Role</label> <label className="form-label">Role</label>
<input type="text" <input
type="text"
{...register("role")} {...register("role")}
className={`form-control ${errors.role ? 'is-invalids' : ''}`} className={`form-control ${errors.role ? "is-invalids" : ""}`}
/> />
{errors.role && <p className="text-danger">{errors.role.message}</p>} {errors.role && <p className="text-danger">{errors.role.message}</p>}
</div> </div>
<div className="col-12 col-md-12"> <div className="col-12 col-md-12">
<label className="form-label" htmlFor="description">Description</label> <label className="form-label" htmlFor="description">
Description
</label>
<textarea <textarea
rows="3" rows="3"
{...register("description")} {...register("description")}
className={`form-control ${errors.description ? 'is-invalids' : ''}`} className={`form-control ${errors.description ? "is-invalids" : ""}`}
onChange={(e) => { onChange={(e) => {
setDescriptionLength(e.target.value.length); setDescriptionLength(e.target.value.length);
// Also update react-hook-form's value
register("description").onChange(e); register("description").onChange(e);
}} }}
></textarea> ></textarea>
@ -131,26 +141,26 @@ const CreateRole = ({ modalType, onClose }) => {
)} )}
</div> </div>
<div className="col-12 col-md-12 border">
<div className="col-12 col-md-12">
{masterFeatures.map((feature, featureIndex) => ( {masterFeatures.map((feature, featureIndex) => (
<React.Fragment key={feature.id}> <React.Fragment key={feature.id}>
<div className="row my-1" key={feature.id} style={{ marginLeft: "0px" }}> <div
className="row my-1"
key={feature.id}
<div className="col-12 col-md-3 d-flex text-start align-items-start" style={{ wordWrap: 'break-word' }}> style={{ marginLeft: "0px" }}
>
<div
className="col-12 col-md-3 d-flex text-start align-items-start"
style={{ wordWrap: "break-word" }}
>
<span className="fs">{feature.name}</span> <span className="fs">{feature.name}</span>
</div> </div>
<div className="col-12 col-md-1"></div> <div className="col-12 col-md-1"></div>
<div className="col-12 col-md-8 d-flex justify-content-start align-items-center flex-wrap"> <div className="col-12 col-md-8 d-flex justify-content-start align-items-center flex-wrap">
{feature.featurePermissions.map((perm, permIndex) => { {feature.featurePermissions.map((perm, permIndex) => {
const refIndex = (featureIndex * 10) + permIndex; const refIndex = featureIndex * 10 + permIndex;
return ( return (
<div className="d-flex me-3 mb-2" key={perm.id}> <div className="d-flex me-3 mb-2" key={perm.id}>
<label className="form-check-label" htmlFor={perm.id}> <label className="form-check-label" htmlFor={perm.id}>
@ -163,15 +173,14 @@ const CreateRole = ({ modalType, onClose }) => {
/> />
{perm.name} {perm.name}
</label> </label>
<div style={{ display: 'flex', alignItems: 'center' }}> <div style={{ display: "flex", alignItems: "center" }}>
<div <div
key={refIndex} key={refIndex}
ref={(el) => ref={(el) => (popoverRefs.current[refIndex] = el)}
(popoverRefs.current[refIndex] = el)
}
tabIndex="0" tabIndex="0"
className="d-flex align-items-center avatar-group justify-content-center" className="d-flex align-items-center avatar-group justify-content-center"
data-bs-toggle="popover" refIndex data-bs-toggle="popover"
refIndex
data-bs-trigger="focus" data-bs-trigger="focus"
data-bs-placement="right" data-bs-placement="right"
data-bs-html="true" data-bs-html="true"
@ -182,23 +191,26 @@ const CreateRole = ({ modalType, onClose }) => {
`} `}
> >
&nbsp; &nbsp;
<svg
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" className="bi bi-info-circle" viewBox="0 0 16 16"> xmlns="http://www.w3.org/2000/svg"
width="13"
height="13"
fill="currentColor"
className="bi bi-info-circle"
viewBox="0 0 16 16"
>
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" /> <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.547 1.11l1.91-2.011c.241-.256.384-.592.287-.984-.172-.439-.58-.827-1.13-.967a.664.664 0 0 1-.58-.309l-.15-.241-.002-.002zM8 4c-.535 0-.943.372-.943.836 0 .464.408.836.943.836.535 0 .943-.372.943-.836 0-.464-.408-.836-.943-.836z" /> <path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.547 1.11l1.91-2.011c.241-.256.384-.592.287-.984-.172-.439-.58-.827-1.13-.967a.664.664 0 0 1-.58-.309l-.15-.241-.002-.002zM8 4c-.535 0-.943.372-.943.836 0 .464.408.836.943.836.535 0 .943-.372.943-.836 0-.464-.408-.836-.943-.836z" />
</svg> </svg>
</div> </div>
</div> </div>
</div> </div>
);
)
})} })}
</div> </div>
</div> </div>
<hr className="hr my-1 py-1" /> <hr className="hr my-1 py-1" />
</React.Fragment> </React.Fragment>
))} ))}
{errors.selectedPermissions && ( {errors.selectedPermissions && (
<p className="text-danger">{errors.selectedPermissions.message}</p> <p className="text-danger">{errors.selectedPermissions.message}</p>
@ -206,33 +218,23 @@ const CreateRole = ({ modalType, onClose }) => {
{!masterFeatures && <p>Loading...</p>} {!masterFeatures && <p>Loading...</p>}
</div> </div>
{masterFeatures && (
{ <div className="col-12 text-center">
masterFeatures && ( <button type="submit" className="btn btn-sm btn-primary me-3">
{isLoading ? "Please Wait..." : "Submit"}
<div className="col-12 text-center"> </button>
<button type="submit" className="btn btn-sm btn-primary me-3"> <button
{isLoading ? "Please Wait..." : "Submit"} type="reset"
</button> className="btn btn-sm btn-label-secondary"
<button data-bs-dismiss="modal"
type="reset" aria-label="Close"
className="btn btn-sm btn-label-secondary" >
data-bs-dismiss="modal" Cancel
aria-label="Close" </button>
> </div>
Cancel )}
</button>
</div>
)
}
</form> </form>
); );
}; };
export default CreateRole; export default CreateRole;

View File

@ -6,6 +6,7 @@ import { MasterRespository } from '../../repositories/MastersRepository';
import { clearApiCacheKey } from '../../slices/apiCacheSlice'; import { clearApiCacheKey } from '../../slices/apiCacheSlice';
import { getCachedData,cacheData } from '../../slices/apiDataManager'; import { getCachedData,cacheData } from '../../slices/apiDataManager';
import showToast from '../../services/toastService'; import showToast from '../../services/toastService';
import {useCreateWorkCategory} from '../../hooks/masterHook/useMaster';
const schema = z.object({ const schema = z.object({
@ -16,58 +17,64 @@ const schema = z.object({
const CreateWorkCategory = ({onClose}) => { const CreateWorkCategory = ({onClose}) => {
const[isLoading,setIsLoading] = useState(false) const {
const { register,
register, handleSubmit,
handleSubmit, formState: { errors },
formState: { errors },reset reset,
} = useForm({
} = useForm({ resolver: zodResolver(schema),
resolver: zodResolver(schema), defaultValues: {
defaultValues: { name: "",
name: "", description: "",
description: "", },
});
},
});
const onSubmit = (data) => {
setIsLoading(true)
// const result = {
// name: data.name,
// description: data.description,
// };
MasterRespository.createWorkCategory(data).then((resp)=>{
setIsLoading(false)
resetForm()
const cachedData = getCachedData("Work Category");
const updatedData = [...cachedData, resp?.data];
cacheData("Work Category", updatedData);
showToast("Work Category Added successfully.", "success");
onClose() const [descriptionLength, setDescriptionLength] = useState(0);
}).catch((error)=>{ const maxDescriptionLength = 255;
showToast(error?.response?.data?.message, "error");
setIsLoading(false) const { mutate: createWorkCategory, isPending: isLoading } = useCreateWorkCategory(() => {
}) resetForm();
onClose?.();
});
const onSubmit = (payload) => {
createWorkCategory(payload)
};
// const onSubmit = (data) => {
// setIsLoading(true)
// MasterRespository.createWorkCategory(data).then((resp)=>{
// setIsLoading(false)
// resetForm()
// const cachedData = getCachedData("Work Category");
// const updatedData = [...cachedData, resp?.data];
// cacheData("Work Category", updatedData);
// showToast("Work Category Added successfully.", "success");
// onClose()
// }).catch((error)=>{
// showToast(error?.response?.data?.message, "error");
// setIsLoading(false)
// })
}; // };
const resetForm = () => {
reset({
name: "",
description: ""
});
setDescriptionLength(0);
}
useEffect(()=>{ const resetForm = () => {
return ()=>resetForm() reset({
},[]) name: "",
description: "",
const [descriptionLength, setDescriptionLength] = useState(0); });
const maxDescriptionLength = 255; setDescriptionLength(0);
};
useEffect(() => {
return () => resetForm();
}, []);
return (<> return (<>
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}> <form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
<div className="col-12 col-md-12"> <div className="col-12 col-md-12">

View File

@ -5,6 +5,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import {MasterRespository} from "../../repositories/MastersRepository"; import {MasterRespository} from "../../repositories/MastersRepository";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import {getCachedData,cacheData} from "../../slices/apiDataManager"; import {getCachedData,cacheData} from "../../slices/apiDataManager";
import {useUpdateActivity} from "../../hooks/masterHook/useMaster";
const schema = z.object({ const schema = z.object({
@ -23,111 +24,107 @@ const schema = z.object({
const UpdateActivity = ({ activityData, onClose }) => { const UpdateActivity = ({ activityData, onClose }) => {
const [isLoading, setIsLoading] = useState(false); const { mutate: updateActivity, isPending: isLoading } = useUpdateActivity(() => onClose?.());
const { const {
register, register,
handleSubmit, handleSubmit,
control, control,
setValue, setValue,
reset, reset,
setError, setError,
clearErrors, clearErrors,
getValues, getValues,
formState: { errors }, formState: { errors },
} = useForm({ } = useForm({
resolver: zodResolver(schema), resolver: zodResolver(schema),
defaultValues: { defaultValues: {
id:activityData.id, id: activityData?.id,
activityName: activityData.activityName, activityName: activityData?.activityName,
unitOfMeasurement: activityData.unitOfMeasurement, unitOfMeasurement: activityData?.unitOfMeasurement,
checkLists: activityData.checkLists || [], checkList: activityData?.checkLists || [],
}, },
}); });
const { fields: checkListItems, append, remove } = useFieldArray({ const { fields: checkListItems, append, remove } = useFieldArray({
control, control,
name: "checkList", name: "checkList",
}); });
// Load initial data useEffect(() => {
useEffect(() => { if (activityData) {
if (activityData) { reset({
reset( { id: activityData.id,
id:activityData.id, activityName: activityData.activityName,
activityName: activityData.activityName, unitOfMeasurement: activityData.unitOfMeasurement,
unitOfMeasurement: activityData.unitOfMeasurement, checkList: activityData.checkLists || [],
checkList: activityData.checkLists || [], });
}); }
} }, [activityData, reset]);
}, [activityData]);
const addChecklistItem = () => {
const handleChecklistChange = (index, value) => { const values = getValues("checkList");
setValue(`checkList.${index}`, value); const lastIndex = checkListItems.length - 1;
if (
checkListItems.length > 0 &&
(!values?.[lastIndex] || values[lastIndex].description.trim() === "")
) {
setError(`checkList.${lastIndex}.description`, {
type: "manual",
message: "Please fill this checklist item before adding another.",
});
return;
}
clearErrors(`checkList.${lastIndex}.description`);
append({ id: null, description: "", isMandatory: false });
};
const removeChecklistItem = (index) => {
remove(index);
};
const handleChecklistChange = (index, value) => {
setValue(`checkList.${index}`, value);
};
const onSubmit = (formData) => {
const payload = { ...formData, id: activityData.id };
updateActivity({ id: activityData.id, payload });
}; };
// const onSubmit = async(data) => {
// Submit handler // setIsLoading(true);
const onSubmit = async(data) => {
setIsLoading(true);
const Activity = {...data, id:activityData.id} // const Activity = {...data, id:activityData.id}
try // try
{ // {
const response = await MasterRespository.updateActivity( activityData?.id, Activity ); // const response = await MasterRespository.updateActivity( activityData?.id, Activity );
const updatedActivity = response.data; // const updatedActivity = response.data;
const cachedData = getCachedData("Activity") // const cachedData = getCachedData("Activity")
if (cachedData) { // if (cachedData) {
const updatedActivities = cachedData.map((activity) => // const updatedActivities = cachedData.map((activity) =>
activity.id === updatedActivity.id ? { ...activity, ...updatedActivity } : activity // activity.id === updatedActivity.id ? { ...activity, ...updatedActivity } : activity
); // );
cacheData( "Activity", updatedActivities ); // cacheData( "Activity", updatedActivities );
onClose() // onClose()
} // }
setIsLoading( false ) // setIsLoading( false )
showToast("Activity Successfully Updated", "success"); // showToast("Activity Successfully Updated", "success");
} catch ( err ) // } catch ( err )
{ // {
setIsLoading( false ) // setIsLoading( false )
showToast("error.message", "error"); // showToast("error.message", "error");
} // }
}; // };
useEffect(() => {
const tooltipTriggerList = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
// Add new checklist item tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el));
const addChecklistItem = () => { }, []);
const values = getValues("checkList");
const lastIndex = checkListItems.length - 1;
if (
checkListItems.length > 0 &&
(!values?.[lastIndex] || values[lastIndex].description.trim() === "")
) {
setError(`checkList.${lastIndex}.description`, {
type: "manual",
message: "Please fill this checklist item before adding another.",
});
return;
}
clearErrors(`checkList.${lastIndex}.description`);
append({ id: null, description: "", isMandatory: false });
};
const removeChecklistItem = (index) => {
remove(index);
};
// for tooltip
useEffect(() => {
const tooltipTriggerList = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el));
}, []);
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
@ -232,7 +229,7 @@ const UpdateActivity = ({ activityData, onClose }) => {
className="btn btn-xs btn-primary mt-2" className="btn btn-xs btn-primary mt-2"
onClick={addChecklistItem} onClick={addChecklistItem}
> >
<i class="bx bx-plus-circle" data-bs-toggle="tooltip" <i className="bx bx-plus-circle" data-bs-toggle="tooltip"
title="Add Check" title="Add Check"
data-bs-original-title="Add check" ></i> data-bs-original-title="Add check" ></i>
</button> </button>
@ -256,4 +253,4 @@ const UpdateActivity = ({ activityData, onClose }) => {
); );
}; };
export default UpdateActivity; export default UpdateActivity;

View File

@ -6,6 +6,7 @@ import { MasterRespository } from '../../repositories/MastersRepository';
import { clearApiCacheKey } from '../../slices/apiCacheSlice'; import { clearApiCacheKey } from '../../slices/apiCacheSlice';
import { getCachedData,cacheData } from '../../slices/apiDataManager'; import { getCachedData,cacheData } from '../../slices/apiDataManager';
import showToast from '../../services/toastService'; import showToast from '../../services/toastService';
import {useUpdateContactCategory} from '../../hooks/masterHook/useMaster';
const schema = z.object({ const schema = z.object({
@ -15,65 +16,72 @@ const schema = z.object({
}); });
const EditContactCategory= ({data,onClose}) => { const EditContactCategory= ({data,onClose}) => {
const {
register,
handleSubmit,
formState: { errors },
reset,
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
name: data?.name || "",
description: data?.description || "",
},
});
const[isLoading,setIsLoading] = useState(false) const [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
const { const maxDescriptionLength = 255;
register,
handleSubmit, const { mutate: updateContactCategory, isPending: isLoading } = useUpdateContactCategory(() => onClose?.());
formState: { errors },reset
const onSubmit = (formData) => {
} = useForm({ const payload = {
resolver: zodResolver(schema), id: data?.id,
defaultValues: { name: formData.name,
name: data?.name || "", description: formData.description,
description:data?.description || "", };
}, updateContactCategory({id:data?.id,payload});
}); };
// const onSubmit = (formdata) => {
const onSubmit = (formdata) => { // setIsLoading(true)
setIsLoading(true) // const result = {
const result = { // id:data?.id,
id:data?.id, // name: formdata?.name,
name: formdata?.name, // description: formdata.description,
description: formdata.description, // };
};
MasterRespository.updateContactCategory(data?.id,result).then((resp)=>{ // MasterRespository.updateContactCategory(data?.id,result).then((resp)=>{
setIsLoading(false) // setIsLoading(false)
showToast("Contact Category Updated successfully.", "success"); // showToast("Contact Category Updated successfully.", "success");
const cachedData = getCachedData("Contact Category"); // const cachedData = getCachedData("Contact Category");
if (cachedData) { // if (cachedData) {
const updatedData = cachedData.map((category) => // const updatedData = cachedData.map((category) =>
category.id === data?.id ? { ...category, ...resp.data } : category // category.id === data?.id ? { ...category, ...resp.data } : category
); // );
cacheData("Contact Category", updatedData); // cacheData("Contact Category", updatedData);
} // }
onClose() // onClose()
}).catch((error)=>{ // }).catch((error)=>{
showToast(error?.response?.data?.message, "error") // showToast(error?.response?.data?.message, "error")
setIsLoading(false) // setIsLoading(false)
}) // })
}; // };
const resetForm = () => {
reset({
name: "",
description: ""
});
setDescriptionLength(0);
}
useEffect(()=>{ const resetForm = () => {
return ()=>resetForm() reset({ name: "", description: "" });
},[]) setDescriptionLength(0);
};
const [descriptionLength, setDescriptionLength] = useState(0);
const maxDescriptionLength = 255; useEffect(() => {
return () => resetForm();
}, []);
return (<> return (<>
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}> <form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
<div className="col-12 col-md-12"> <div className="col-12 col-md-12">

View File

@ -6,6 +6,7 @@ import { MasterRespository } from '../../repositories/MastersRepository';
import { clearApiCacheKey } from '../../slices/apiCacheSlice'; import { clearApiCacheKey } from '../../slices/apiCacheSlice';
import { getCachedData,cacheData } from '../../slices/apiDataManager'; import { getCachedData,cacheData } from '../../slices/apiDataManager';
import showToast from '../../services/toastService'; import showToast from '../../services/toastService';
import {useUpdateContactTag} from '../../hooks/masterHook/useMaster';
const schema = z.object({ const schema = z.object({
@ -15,65 +16,72 @@ const schema = z.object({
}); });
const EditContactTag= ({data,onClose}) => { const EditContactTag= ({data,onClose}) => {
const {
register,
handleSubmit,
formState: { errors },
reset
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
name: data?.name || "",
description: data?.description || "",
},
});
const[isLoading,setIsLoading] = useState(false) const [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
const { const maxDescriptionLength = 255;
register,
handleSubmit, const { mutate: updateContactTag, isPending: isLoading } = useUpdateContactTag(() => onClose?.());
formState: { errors },reset
const onSubmit = (formData) => {
} = useForm({ const payload = {
resolver: zodResolver(schema), id: data?.id,
defaultValues: { name: formData.name,
name: data?.name || "", description: formData.description,
description:data?.description || "", };
debugger
}, updateContactTag({ id: data?.id, payload} );
}); }
// const onSubmit = (formdata) => {
const onSubmit = (formdata) => { // setIsLoading(true)
setIsLoading(true) // const result = {
const result = { // id:data?.id,
id:data?.id, // name: formdata?.name,
name: formdata?.name, // description: formdata.description,
description: formdata.description, // };
};
MasterRespository.updateContactTag(data?.id,result).then((resp)=>{ // MasterRespository.updateContactTag(data?.id,result).then((resp)=>{
setIsLoading(false) // setIsLoading(false)
showToast("Contact Tag Updated successfully.", "success"); // showToast("Contact Tag Updated successfully.", "success");
const cachedData = getCachedData("Contact Tag"); // const cachedData = getCachedData("Contact Tag");
if (cachedData) { // if (cachedData) {
const updatedData = cachedData.map((category) => // const updatedData = cachedData.map((category) =>
category.id === data?.id ? { ...category, ...resp.data } : category // category.id === data?.id ? { ...category, ...resp.data } : category
); // );
cacheData("Contact Tag", updatedData); // cacheData("Contact Tag", updatedData);
} // }
onClose() // onClose()
}).catch((error)=>{ // }).catch((error)=>{
showToast(error?.response?.data?.message, "error") // showToast(error?.response?.data?.message, "error")
setIsLoading(false) // setIsLoading(false)
}) // })
}; // };
const resetForm = () => {
reset({
name: "",
description: ""
});
setDescriptionLength(0);
}
useEffect(()=>{ const resetForm = () => {
return ()=>resetForm() reset({ name: "", description: "" });
},[]) setDescriptionLength(0);
};
const [descriptionLength, setDescriptionLength] = useState(0);
const maxDescriptionLength = 255; useEffect(() => {
return () => resetForm();
}, []);
return (<> return (<>
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}> <form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
<div className="col-12 col-md-12"> <div className="col-12 col-md-12">

View File

@ -5,6 +5,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { MasterRespository } from '../../repositories/MastersRepository'; import { MasterRespository } from '../../repositories/MastersRepository';
import { cacheData,getCachedData } from '../../slices/apiDataManager'; import { cacheData,getCachedData } from '../../slices/apiDataManager';
import showToast from '../../services/toastService'; import showToast from '../../services/toastService';
import {useUpdateJobRole} from '../../hooks/masterHook/useMaster';
@ -17,63 +18,81 @@ const schema = z.object({
const EditJobRole = ({data,onClose}) => { const EditJobRole = ({data,onClose}) => {
const [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
const[isLoading,setIsLoading] = useState(false)
const {
register,
handleSubmit,
formState: { errors } ,reset
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
role: data?.name || "",
description:data?.description || "",
},
});
const onSubmit = (formdata) => {
setIsLoading(true)
const result = {
id:data?.id,
name: formdata?.role,
description: formdata.description,
};
MasterRespository.updateJobRole(data?.id,result).then((resp)=>{
setIsLoading(false)
showToast("JobRole Update successfully.", "success");
const cachedData = getCachedData("Job Role");
if (cachedData) {
const updatedData = cachedData.map((role) =>
role.id === data?.id ? { ...role, ...resp.data } : role
);
cacheData("Job Role", updatedData);
}
onClose()
}).catch((error)=>{
showToast(error.message, "error")
setIsLoading(false)
})
};
useEffect(() => {
reset({
role: data?.name,
description: data?.description
});
setDescriptionLength(data?.description?.length || 0);
}, [data, reset]);
const [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
const maxDescriptionLength = 255; const maxDescriptionLength = 255;
const {
register,
handleSubmit,
formState: { errors },
reset,
watch,
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
role: data?.name || "",
description: data?.description || "",
},
});
const { mutate: updateJobRole, isPendin:isLoading } = useUpdateJobRole(() => {
onClose?.();
});
// const onSubmit = (formdata) => {
// setIsLoading(true)
// const result = {
// id:data?.id,
// name: formdata?.role,
// description: formdata.description,
// };
// MasterRespository.updateJobRole(data?.id,result).then((resp)=>{
// setIsLoading(false)
// showToast("JobRole Update successfully.", "success");
// const cachedData = getCachedData("Job Role");
// if (cachedData) {
// const updatedData = cachedData.map((role) =>
// role.id === data?.id ? { ...role, ...resp.data } : role
// );
// cacheData("Job Role", updatedData);
// }
// onClose()
// }).catch((error)=>{
// showToast(error.message, "error")
// setIsLoading(false)
// })
// };
const onSubmit = (formData) => {
updateJobRole({
id: data?.id,
payload: {
id: data?.id,
name: formData.role,
description: formData.description,
},
});
};
useEffect(() => {
reset({
role: data?.name || "",
description: data?.description || "",
});
setDescriptionLength(data?.description?.length || 0);
}, [data, reset]);
useEffect(() => {
const sub = watch((values) => {
setDescriptionLength(values.description?.length || 0);
});
return () => sub.unsubscribe();
}, [watch]);
return (<> return (<>
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}> <form className="row g-2" onSubmit={handleSubmit(onSubmit)}>

View File

@ -7,6 +7,7 @@ import { useFeatures } from "../../hooks/useMasterRole";
import { MasterRespository } from "../../repositories/MastersRepository"; import { MasterRespository } from "../../repositories/MastersRepository";
import { cacheData, getCachedData } from "../../slices/apiDataManager"; import { cacheData, getCachedData } from "../../slices/apiDataManager";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import {useUpdateApplicationRole} from "../../hooks/masterHook/useMaster";
@ -27,128 +28,149 @@ const updateSchema = z.object({
const EditMaster = ({ master, onClose }) => { const EditMaster = ({ master, onClose }) => {
const [isLoading, setIsLoading] = useState(false) const maxDescriptionLength = 255;
const { masterFeatures } = useFeatures() const popoverRefs = useRef([]);
const popoverRefs = useRef([]); const { masterFeatures } = useFeatures();
const { mutate: updateApplicationRole, isPending: isLoading } = useUpdateApplicationRole(() => onClose?.());
const buildDefaultPermissions = () => { const buildDefaultPermissions = () => {
const defaults = {}; const defaults = {};
masterFeatures.forEach((feature) => { masterFeatures.forEach((feature) => {
feature.featurePermissions.forEach((perm) => { feature.featurePermissions.forEach((perm) => {
const existing = master?.item?.featurePermission?.find( const existing = master?.item?.featurePermission?.find(p => p.id === perm.id);
(p) => p.id === perm.id defaults[perm.id] = existing?.isEnabled || false;
);
defaults[perm.id] = existing ? existing.isEnabled : false;
});
}); });
return defaults;
};
const initialPermissions = buildDefaultPermissions();
const {
register,
handleSubmit,
formState: { errors, dirtyFields },
setError, reset
} = useForm({
resolver: zodResolver(updateSchema),
defaultValues: {
role: master?.item?.role,
description: master?.item?.description,
permissions: initialPermissions,
},
}); });
return defaults;
};
const onSubmit = (data) => { const initialPermissions = buildDefaultPermissions();
setIsLoading(true)
const existingIds = new Set(
master?.item?.featurePermission?.map((p) => p.id)
);
const {
register,
handleSubmit,
formState: { errors, dirtyFields },
setError,
reset,
} = useForm({
resolver: zodResolver(updateSchema),
defaultValues: {
role: master?.item?.role || "",
description: master?.item?.description || "",
permissions: initialPermissions,
},
});
const updatedPermissions = Object.entries(data.permissions) const [descriptionLength, setDescriptionLength] = useState(master?.item?.description?.length || 0);
.filter(([id, value]) => {
if (existingIds.has(id)) return true;
return ( const onSubmit = (data) => {
value === true || const existingIds = new Set(master?.item?.featurePermission?.map(p => p.id));
(dirtyFields.permissions && dirtyFields.permissions[id])
);
})
.map(([id, value]) => ({ id, isEnabled: value }));
const updatedPermissions = Object.entries(data.permissions)
if (updatedPermissions.length === 0) { .filter(([id, value]) => {
setError("permissions", { return existingIds.has(id) || value === true || (dirtyFields.permissions?.[id]);
type: "manual",
message: "At least one permission must be selected.",
});
return;
}
const updatedRole = {
id: master?.item?.id,
role: data.role,
description: data.description,
featuresPermission: updatedPermissions,
};
MasterRespository.updateRoles(master?.item?.id, updatedRole).then((resp) => {
setIsLoading(false)
const cachedData = getCachedData("Application Role");
if (cachedData) {
const updatedData = cachedData.map((role) =>
role.id === resp.data?.id ? { ...role, ...resp.data } : role
);
cacheData("Application Role", updatedData);
}
showToast("Application Role Updated successfully.", "success");
setIsLoading(false)
onClose()
}).catch((Err) => {
showToast(Err.message, "error");
setIsLoading(false)
}) })
.map(([id, value]) => ({ id, isEnabled: value }));
if (updatedPermissions.length === 0) {
setError("permissions", {
type: "manual",
message: "At least one permission must be selected.",
});
return;
}
const payload = {
id: master?.item?.id,
role: data.role,
description: data.description,
featuresPermission: updatedPermissions,
}; };
useEffect(() => { updateApplicationRole({ id: payload.id, payload });
reset({ };
role: master?.item?.role, // const onSubmit = (data) => {
description: master?.item?.description, // setIsLoading(true)
permissions: initialPermissions, // const existingIds = new Set(
}); // master?.item?.featurePermission?.map((p) => p.id)
setDescriptionLength(master?.item?.description?.length || 0); // );
}, [master, reset]);
const [descriptionLength, setDescriptionLength] = useState(master?.item?.description?.length || 0);
const maxDescriptionLength = 255;
useEffect(() => { // const updatedPermissions = Object.entries(data.permissions)
popoverRefs.current.forEach((el) => { // .filter(([id, value]) => {
if (el) { // if (existingIds.has(id)) return true;
new bootstrap.Popover(el, {
trigger: "focus", // return (
placement: "right", // value === true ||
html: true, // (dirtyFields.permissions && dirtyFields.permissions[id])
content: el.getAttribute("data-bs-content"), // use inline content from attribute // );
}); // })
} // .map(([id, value]) => ({ id, isEnabled: value }));
});
}, [masterFeatures]);
// if (updatedPermissions.length === 0) {
// setError("permissions", {
// type: "manual",
// message: "At least one permission must be selected.",
// });
// return;
// }
// const updatedRole = {
// id: master?.item?.id,
// role: data.role,
// description: data.description,
// featuresPermission: updatedPermissions,
// };
// MasterRespository.updateRoles(master?.item?.id, updatedRole).then((resp) => {
// setIsLoading(false)
// const cachedData = getCachedData("Application Role");
// if (cachedData) {
// const updatedData = cachedData.map((role) =>
// role.id === resp.data?.id ? { ...role, ...resp.data } : role
// );
// cacheData("Application Role", updatedData);
// }
// showToast("Application Role Updated successfully.", "success");
// setIsLoading(false)
// onClose()
// }).catch((Err) => {
// showToast(Err.message, "error");
// setIsLoading(false)
// })
// };
useEffect(() => {
reset({
role: master?.item?.role || "",
description: master?.item?.description || "",
permissions: buildDefaultPermissions(),
});
setDescriptionLength(master?.item?.description?.length || 0);
}, [master, reset]);
useEffect(() => {
popoverRefs.current.forEach((el) => {
if (el) {
new bootstrap.Popover(el, {
trigger: "focus",
placement: "right",
html: true,
content: el.getAttribute("data-bs-content"),
});
}
});
}, [masterFeatures]);
return ( return (
<form className="row g-2 " onSubmit={handleSubmit(onSubmit)}> <form className="row g-2 " onSubmit={handleSubmit(onSubmit)}>
{/* <div className="col-12 col-md-12">
<label className="fs-5 text-dark text-center d-flex align-items-center justify-content-center flex-wrap">Edit Application Role</label>
</div> */}
<div className="col-12 col-md-12"> <div className="col-12 col-md-12">
<label className="form-label">Role</label> <label className="form-label">Role</label>
<input type="text" <input type="text"

View File

@ -5,6 +5,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { MasterRespository } from '../../repositories/MastersRepository'; import { MasterRespository } from '../../repositories/MastersRepository';
import { cacheData,getCachedData } from '../../slices/apiDataManager'; import { cacheData,getCachedData } from '../../slices/apiDataManager';
import showToast from '../../services/toastService'; import showToast from '../../services/toastService';
import {useUpdateWorkCategory} from '../../hooks/masterHook/useMaster';
@ -18,69 +19,75 @@ const schema = z.object({
const EditWorkCategory = ({data,onClose}) => { const EditWorkCategory = ({data,onClose}) => {
const[isLoading,setIsLoading] = useState(false) const {
const { register,
register, handleSubmit,
handleSubmit, formState: { errors },
formState: { errors } ,reset reset,
} = useForm({ } = useForm({
resolver: zodResolver(schema), resolver: zodResolver(schema),
defaultValues: { defaultValues: {
name: data?.name || "", name: data?.name || "",
description:data?.description || "", description: data?.description || "",
},
}, });
});
const [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
const onSubmit = (formdata) => { const maxDescriptionLength = 255;
setIsLoading(true)
const result = { const { mutate: updateWorkCategory, isPending: isLoading } = useUpdateWorkCategory(() => onClose?.());
id:data?.id,
name: formdata?.name, const onSubmit = (formdata) => {
description: formdata.description, const payload = {
}; id: data?.id,
name: formdata.name,
description: formdata.description,
};
updateWorkCategory({id:data?.id,payload});
};
// const onSubmit = (formdata) => {
// setIsLoading(true)
// const result = {
// id:data?.id,
// name: formdata?.name,
// description: formdata.description,
// };
MasterRespository.updateWorkCategory(data?.id,result).then((resp)=>{ // MasterRespository.updateWorkCategory(data?.id,result).then((resp)=>{
setIsLoading(false) // setIsLoading(false)
showToast("Work Category Update successfully.", "success"); // showToast("Work Category Update successfully.", "success");
const cachedData = getCachedData("Work Category"); // const cachedData = getCachedData("Work Category");
if (cachedData) { // if (cachedData) {
const updatedData = cachedData.map((category) => // const updatedData = cachedData.map((category) =>
category.id === data?.id ? { ...category, ...resp.data } : category // category.id === data?.id ? { ...category, ...resp.data } : category
); // );
cacheData("Work Category", updatedData); // cacheData("Work Category", updatedData);
} // }
onClose() // onClose()
}).catch((error)=>{ // }).catch((error)=>{
showToast(error?.response?.data?.message, "error") // showToast(error?.response?.data?.message, "error")
setIsLoading(false) // setIsLoading(false)
}) // })
}; // };
useEffect(() => {
reset({
name: data?.name || "",
description: data?.description || "",
});
setDescriptionLength(data?.description?.length || 0);
}, [data, reset]);
useEffect(() => {
reset({
name: data?.name,
description: data?.description
});
setDescriptionLength(data?.description?.length || 0);
}, [data, reset]);
const [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
const maxDescriptionLength = 255;
return (<> return (<>
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}> <form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
{/* <div className="col-12 col-md-12">
<label className="fs-5 text-dark text-center d-flex align-items-center justify-content-center flex-wrap">Edit Work Category</label>
</div>
*/}
<div className="col-12 col-md-12"> <div className="col-12 col-md-12">
<label className="form-label">Category Name</label> <label className="form-label">Category Name</label>
<input type="text" <input type="text"

View File

@ -8,8 +8,8 @@ import EditJobRole from "./EditJobRole";
import CreateActivity from "./CreateActivity"; import CreateActivity from "./CreateActivity";
import EditActivity from "./EditActivity"; import EditActivity from "./EditActivity";
import ConfirmModal from "../common/ConfirmModal"; import ConfirmModal from "../common/ConfirmModal";
import {MasterRespository} from "../../repositories/MastersRepository"; import { MasterRespository } from "../../repositories/MastersRepository";
import {cacheData, getCachedData} from "../../slices/apiDataManager"; import { cacheData, getCachedData } from "../../slices/apiDataManager";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import CreateWorkCategory from "./CreateWorkCategory"; import CreateWorkCategory from "./CreateWorkCategory";
import EditWorkCategory from "./EditWorkCategory"; import EditWorkCategory from "./EditWorkCategory";
@ -17,35 +17,55 @@ import CreateCategory from "./CreateContactCategory";
import CreateContactTag from "./CreateContactTag"; import CreateContactTag from "./CreateContactTag";
import EditContactCategory from "./EditContactCategory"; import EditContactCategory from "./EditContactCategory";
import EditContactTag from "./EditContactTag"; import EditContactTag from "./EditContactTag";
import { useDeleteMasterItem } from "../../hooks/masterHook/useMaster";
const MasterModal = ({ modaldata, closeModal }) => { const MasterModal = ({ modaldata, closeModal }) => {
const [ isDeleteModalOpen, setIsDeleteModalOpen ] = useState( false ); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const { mutate: deleteMasterItem, isPending } = useDeleteMasterItem();
const handleSelectedMasterDeleted = async () => // const handleSelectedMasterDeleted = async () =>
{ // {
const deleteFn = MasterRespository[modaldata.masterType]; // debugger
if (!deleteFn) { // const deleteFn = MasterRespository[modaldata.masterType];
showToast(`No delete strategy defined for master type`,"error"); // if (!deleteFn) {
return false; // showToast(`No delete strategy defined for master type`,"error");
// return false;
// }
// try
// {
// const response = await deleteFn( modaldata?.item?.id );
// const selected_cachedData = getCachedData( modaldata?.masterType );
// const updated_master = selected_cachedData?.filter(item => item.id !== modaldata?.item.id);
// cacheData( modaldata?.masterType, updated_master )
// showToast(`${modaldata?.masterType} is deleted successfully`, "success");
// handleCloseDeleteModal()
// } catch ( error )
// {
// const message = error.response.data.message || error.message || "Error occured api during call"
// showToast(message, "success");
// }
// }
const handleSelectedMasterDeleted = () => {
if (!modaldata?.masterType || !modaldata?.item?.id) {
showToast("Missing master type or item", "error");
return;
} }
try deleteMasterItem(
{ {
const response = await deleteFn( modaldata?.item?.id ); masterType: modaldata.masterType,
const selected_cachedData = getCachedData( modaldata?.masterType ); item: modaldata.item,
const updated_master = selected_cachedData?.filter(item => item.id !== modaldata?.item.id); validateFn: modaldata.validateFn, // optional
cacheData( modaldata?.masterType, updated_master ) },
{
showToast(`${modaldata?.masterType} is deleted successfully`, "success"); onSuccess: () => {
handleCloseDeleteModal() handleCloseDeleteModal();
},
} catch ( error ) }
{ );
const message = error.response.data.message || error.message || "Error occured api during call" };
showToast(message, "success");
}
}
useEffect(() => { useEffect(() => {
if (modaldata?.modalType === "delete") { if (modaldata?.modalType === "delete") {
@ -55,7 +75,7 @@ const MasterModal = ({ modaldata, closeModal }) => {
const handleCloseDeleteModal = () => { const handleCloseDeleteModal = () => {
setIsDeleteModalOpen(false); setIsDeleteModalOpen(false);
closeModal(); closeModal();
}; };
if (modaldata?.modalType === "delete" && isDeleteModalOpen) { if (modaldata?.modalType === "delete" && isDeleteModalOpen) {
@ -88,7 +108,9 @@ const MasterModal = ({ modaldata, closeModal }) => {
> >
<div <div
className={`modal-dialog mx-sm-auto mx-1 ${ className={`modal-dialog mx-sm-auto mx-1 ${
["Application Role", "Edit-Application Role"].includes(modaldata?.modalType) ["Application Role", "Edit-Application Role"].includes(
modaldata?.modalType
)
? "modal-lg" ? "modal-lg"
: "modal-md" : "modal-md"
} modal-simple`} } modal-simple`}
@ -96,18 +118,21 @@ const MasterModal = ({ modaldata, closeModal }) => {
<div className="modal-content"> <div className="modal-content">
<div className="modal-body p-sm-4 p-0"> <div className="modal-body p-sm-4 p-0">
<div className="d-flex justify-content-between"> <div className="d-flex justify-content-between">
<h6>{ `${modaldata?.modalType} `}</h6> <h6>{`${modaldata?.modalType} `}</h6>
<button <button
type="button" type="button"
className="btn-close" className="btn-close"
data-bs-dismiss="modal" data-bs-dismiss="modal"
aria-label="Close" aria-label="Close"
onClick={closeModal} onClick={closeModal}
></button> ></button>
</div> </div>
{modaldata.modalType === "Application Role" && ( {modaldata.modalType === "Application Role" && (
<CreateRole masmodalType={modaldata.masterType} onClose={closeModal} /> <CreateRole
masmodalType={modaldata.masterType}
onClose={closeModal}
/>
)} )}
{modaldata.modalType === "Edit-Application Role" && ( {modaldata.modalType === "Edit-Application Role" && (
<EditRole master={modaldata} onClose={closeModal} /> <EditRole master={modaldata} onClose={closeModal} />
@ -122,7 +147,10 @@ const MasterModal = ({ modaldata, closeModal }) => {
<CreateActivity onClose={closeModal} /> <CreateActivity onClose={closeModal} />
)} )}
{modaldata.modalType === "Edit-Activity" && ( {modaldata.modalType === "Edit-Activity" && (
<EditActivity activityData={modaldata.item} onClose={closeModal} /> <EditActivity
activityData={modaldata.item}
onClose={closeModal}
/>
)} )}
{modaldata.modalType === "Work Category" && ( {modaldata.modalType === "Work Category" && (
<CreateWorkCategory onClose={closeModal} /> <CreateWorkCategory onClose={closeModal} />
@ -133,10 +161,10 @@ const MasterModal = ({ modaldata, closeModal }) => {
{modaldata.modalType === "Contact Category" && ( {modaldata.modalType === "Contact Category" && (
<CreateCategory data={modaldata.item} onClose={closeModal} /> <CreateCategory data={modaldata.item} onClose={closeModal} />
)} )}
{modaldata.modalType === "Edit-Contact Category" && ( {modaldata.modalType === "Edit-Contact Category" && (
<EditContactCategory data={modaldata.item} onClose={closeModal} /> <EditContactCategory data={modaldata.item} onClose={closeModal} />
)} )}
{modaldata.modalType === "Contact Tag" && ( {modaldata.modalType === "Contact Tag" && (
<CreateContactTag data={modaldata.item} onClose={closeModal} /> <CreateContactTag data={modaldata.item} onClose={closeModal} />
)} )}
{modaldata.modalType === "Edit-Contact Tag" && ( {modaldata.modalType === "Edit-Contact Tag" && (

View File

@ -1,7 +1,9 @@
import {useState,useEffect} from "react" import {useState,useEffect, useCallback} from "react"
import { MasterRespository } from "../../repositories/MastersRepository"; import { MasterRespository } from "../../repositories/MastersRepository";
import { cacheData,getCachedData } from "../../slices/apiDataManager"; import { cacheData,getCachedData } from "../../slices/apiDataManager";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import {useMutation, useQueries, useQuery, useQueryClient} from "@tanstack/react-query";
import showToast from "../../services/toastService";
@ -9,214 +11,687 @@ import { useSelector } from "react-redux";
export const useFeatures = () =>
useMasterData("masterFeatures", MasterRespository.getFeatures);
export const useMasterRole = () => // const useMaster = () => {
useMasterData("masterRole", MasterRespository.getRoles);
// const selectedMaster = useSelector((store)=>store.localVariables.selectedMaster);
const useMaster = (isMa) => { // const [data, setData] = useState([]);
// const [loading, setLoading] = useState(true);
const selectedMaster = useSelector((store)=>store.localVariables.selectedMaster); // const [error, setError] = useState("");
const [data, setData] = useState([]); // useEffect(() => {
const [loading, setLoading] = useState(true); // const fetchData = async () => {
const [error, setError] = useState(""); // if (!selectedMaster) return;
useEffect(() => { // setLoading(true);
const fetchData = async () => { // try {
if (!selectedMaster) return; // const cachedData = getCachedData(selectedMaster);
setLoading(true); // if (cachedData) {
try {
const cachedData = getCachedData(selectedMaster);
if (cachedData) {
setData(cachedData); // setData(cachedData);
} else { // } else {
let response; // let response;
switch (selectedMaster) { // switch (selectedMaster) {
case "Application Role": // case "Application Role":
response = await MasterRespository.getRoles(); // response = await MasterRespository.getRoles();
response = response.data; // response = response.data;
break; // break;
case "Job Role": // case "Job Role":
response = await MasterRespository.getJobRole(); // response = await MasterRespository.getJobRole();
response = response.data // response = response.data
break; // break;
case "Activity": // case "Activity":
response = await MasterRespository.getActivites(); // response = await MasterRespository.getActivites();
response = response.data // response = response.data
break; // break;
case "Work Category": // case "Work Category":
response = await MasterRespository.getWorkCategory(); // response = await MasterRespository.getWorkCategory();
response = response.data // response = response.data
break; // break;
case "Contact Category": // case "Contact Category":
response = await MasterRespository.getContactCategory(); // response = await MasterRespository.getContactCategory();
response = response.data // response = response.data
break; // break;
case "Contact Tag": // case "Contact Tag":
response = await MasterRespository.getContactTag(); // response = await MasterRespository.getContactTag();
response = response.data // response = response.data
break; // break;
case "Status": // case "Status":
response = [{description: null,featurePermission: null,id: "02dd4761-363c-49ed-8851-3d2489a3e98d",status:"status 1"},{description: null,featurePermission: null,id: "03dy9761-363c-49ed-8851-3d2489a3e98d",status:"status 2"},{description: null,featurePermission: null,id: "03dy7761-263c-49ed-8851-3d2489a3e98d",status:"Status 3"}]; // response = [{description: null,featurePermission: null,id: "02dd4761-363c-49ed-8851-3d2489a3e98d",status:"status 1"},{description: null,featurePermission: null,id: "03dy9761-363c-49ed-8851-3d2489a3e98d",status:"status 2"},{description: null,featurePermission: null,id: "03dy7761-263c-49ed-8851-3d2489a3e98d",status:"Status 3"}];
break; // break;
default: // default:
response = []; // response = [];
} // }
if (response) { // if (response) {
setData(response); // setData(response);
cacheData(selectedMaster, response); // cacheData(selectedMaster, response);
} // }
} // }
} catch (err) { // } catch (err) {
setError("Failed to fetch data."); // setError("Failed to fetch data.");
} finally { // } finally {
setLoading(false); // setLoading(false);
} // }
}; // };
if ( selectedMaster ) // if ( selectedMaster )
{ // {
fetchData(); // fetchData();
} // }
}, [selectedMaster]); // }, [selectedMaster]);
return { data, loading, error } // return { data, loading, error }
// };
// export const useActivitiesMaster = () =>
// {
// const [ activities, setActivites ] = useState( [] )
// const [ loading, setloading ] = useState( false );
// const [ error, setError ] = useState()
// const fetchActivities =async () => {
// setloading(true);
// try {
// const response = await MasterRespository.getActivites();
// setActivites(response.data);
// cacheData( "ActivityMaster", response.data );
// setloading(false);
// } catch (err) {
// setError(err);
// setloading(false);
// }
// }
// useEffect( () =>
// {
// const cacheddata = getCachedData( "ActivityMaster" );
// if ( !cacheddata )
// {
// fetchActivities()
// } else
// {
// setActivites(cacheddata);
// }
// }, [] )
// return {activities,loading,error}
// }
// export const useWorkCategoriesMaster = () =>
// {
// const [ categories, setCategories ] = useState( [] )
// const [ categoryLoading, setloading ] = useState( false );
// const [ categoryError, setError ] = useState( "" )
// const fetchCategories =async () => {
// const cacheddata = getCachedData("Work Category");
// if (!cacheddata) {
// setloading(true);
// try {
// const response = await MasterRespository.getWorkCategory();
// setCategories(response.data);
// cacheData("Work Category", response.data);
// } catch (err) {
// setError(err);
// console.log(err);
// } finally {
// setloading(false);
// }
// } else {
// setCategories(cacheddata);
// }
// }
// useEffect( () =>
// {
// fetchCategories()
// }, [] )
// return {categories,categoryLoading,categoryError}
// }
// export const useContactCategory = () =>
// {
// const [ contactCategory, setContactCategory ] = useState( [] )
// const [ loading, setLoading ] = useState( false )
// const [ Error, setError ] = useState()
// const fetchConatctCategory = async() =>
// {
// const cache_Category = getCachedData( "Contact Category" );
// if ( !cache_Category )
// {
// try
// {
// let resp = await MasterRespository.getContactCategory();
// setContactCategory( resp.data );
// cacheData("Contact Category",resp.data)
// } catch ( error )
// {
// setError(error)
// }
// } else
// {
// setContactCategory(cache_Category)
// }
// }
// useEffect( () =>
// {
// fetchConatctCategory()
// }, [] )
// return { contactCategory,loading,Error}
// }
// export const useContactTags = () => {
// const [contactTags, setContactTags] = useState([]);
// const [loading, setLoading] = useState(false);
// const [error, setError] = useState(null);
// useEffect(() => {
// const fetchContactTag = async () => {
// const cache_Tags = getCachedData("Contact Tag");
// if (!cache_Tags) {
// setLoading(true);
// try {
// const resp = await MasterRespository.getContactTag();
// setContactTags(resp.data);
// cacheData("Contact Tag", resp.data);
// } catch (err) {
// setError(err);
// } finally {
// setLoading(false);
// }
// } else {
// setContactTags(cache_Tags);
// }
// };
// fetchContactTag();
// }, []);
// return { contactTags, loading, error };
// };
// Separate matser-------------
export const useActivitiesMaster = () =>
{
const { data:activities=[],isLoading:loading,error} = useQuery( {
queryKey: [ "ActivityMaster" ],
queryFn: async() =>
{
const response = await MasterRespository.getActivites();
return response.data
},
onError: (error) => {
showToast(error?.response?.data?.message || error.message || "Failed to fetch ActivityMasters", "error");
},
} )
return {activities,loading,error}
}
export const useWorkCategoriesMaster = () => {
const {
data: categories = [],
isLoading: categoryLoading,
error: categoryError,
} = useQuery({
queryKey: ["Work Category"],
queryFn: async () => {
const response = await MasterRespository.getWorkCategory();
return response.data;
},
onError: (error) => {
showToast(
error?.response?.data?.message ||
error.message ||
"Failed to fetch work categories",
"error"
);
},
});
return { categories, categoryLoading, categoryError };
};
export const useContactCategory = () =>
{
const {data:contactCategory=[],isLoading:loading,error:Error } = useQuery( {
queryKey: [ "Contact Category" ],
queryFn: async () =>
{
let resp = await MasterRespository.getContactCategory();
return resp.data
},
onError: (error) => {
showToast(error?.response?.data?.message || error.message || "Failed to fetch Contact categories", "error");
},
} )
return { contactCategory,loading,Error}
}
export const useContactTags = () => {
const {
data: contactTags = [],
isLoading: loading,
error,
} = useQuery({
queryKey: ["Contact Tag"],
queryFn: async () => {
const res = await MasterRespository.getContactTag();
return res.data;
},
onError: (error) => {
showToast(
error?.response?.data?.message ||
error.message ||
"Failed to fetch Contact Tag",
"error"
);
},
});
return { contactTags, loading, error };
};
// ===Application Masters Query=================================================
const fetchMasterData = async (masterType) => {
switch (masterType) {
case "Application Role":
return (await MasterRespository.getRoles()).data;
case "Job Role":
return (await MasterRespository.getJobRole()).data;
case "Activity":
return (await MasterRespository.getActivites()).data;
case "Work Category":
return (await MasterRespository.getWorkCategory()).data;
case "Contact Category":
return (await MasterRespository.getContactCategory()).data;
case "Contact Tag":
return (await MasterRespository.getContactTag()).data;
case "Status":
return [
{
description: null,
featurePermission: null,
id: "02dd4761-363c-49ed-8851-3d2489a3e98d",
status: "status 1",
},
{
description: null,
featurePermission: null,
id: "03dy9761-363c-49ed-8851-3d2489a3e98d",
status: "status 2",
},
{
description: null,
featurePermission: null,
id: "03dy7761-263c-49ed-8851-3d2489a3e98d",
status: "Status 3",
},
];
default:
return [];
}
};
const useMaster = () => {
const selectedMaster = useSelector((store) => store.localVariables.selectedMaster);
const queryFn = useCallback(() => fetchMasterData(selectedMaster), [selectedMaster]);
const {
data = [],
isLoading,
error,
} = useQuery({
queryKey: ["masterData", selectedMaster],
queryFn,
enabled: !!selectedMaster,
staleTime: 1000 * 60 * 10,
refetchOnWindowFocus: false,
onError: (error) => {
showToast(error?.response?.data?.message || error.message || `Failed to fetch ${selectedMaster} Maseter`, "error");
},
});
return { data, loading: isLoading, error };
}; };
export default useMaster; export default useMaster;
export const useActivitiesMaster = () => // ================================Mutation====================================
{
const [ activities, setActivites ] = useState( [] )
const [ loading, setloading ] = useState( false );
const [ error, setError ] = useState()
const fetchActivities =async () => {
setloading(true);
try {
const response = await MasterRespository.getActivites();
setActivites(response.data);
cacheData( "ActivityMaster", response.data );
setloading(false);
} catch (err) {
setError(err);
setloading(false);
}
}
useEffect( () =>
{
const cacheddata = getCachedData( "ActivityMaster" );
if ( !cacheddata )
{
fetchActivities()
} else
{
setActivites(cacheddata);
}
}, [] )
return {activities,loading,error}
}
export const useWorkCategoriesMaster = () => // Job Role-----------------------------------
{ export const useUpdateJobRole = (onSuccessCallback, onErrorCallback) => {
const [ categories, setCategories ] = useState( [] ) const queryClient = useQueryClient();
const [ categoryLoading, setloading ] = useState( false );
const [ categoryError, setError ] = useState( "" ) return useMutation({
mutationFn: async ({ id, payload }) => {
const fetchCategories =async () => { const response = await MasterRespository.updateJobRole(id, payload);
const cacheddata = getCachedData("Work Category"); return response.data;
},
if (!cacheddata) { onSuccess: (data, variables) => {
setloading(true); showToast("JobRole updated successfully.", "success");
try {
const response = await MasterRespository.getWorkCategory(); queryClient.invalidateQueries({
setCategories(response.data); queryKey: ["masterData", "Job Role"],
cacheData("Work Category", response.data); });
} catch (err) {
setError(err); if (onSuccessCallback) onSuccessCallback(data);
console.log(err); },
} finally { onError: (error) => {
setloading(false); showToast(error.message || "Something went wrong", "error");
} if (onErrorCallback) onErrorCallback(error);
} else { },
setCategories(cacheddata); });
} };
}
useEffect( () => export const useCreateJobRole = (onSuccessCallback) => {
{ const queryClient = useQueryClient();
fetchCategories()
}, [] ) return useMutation({
mutationFn: async (payload) => {
return {categories,categoryLoading,categoryError} const response = await MasterRespository.createJobRole(payload);
} return response.data;
},
export const useContactCategory = () => onSuccess: (data) => {
showToast("JobRole added successfully.", "success");
queryClient.invalidateQueries({queryKey:["masterData", "Job Role"]});
if (onSuccessCallback) onSuccessCallback(data);
},
onError: (error) => {
showToast(error.message || "Something went wrong", "error");
},
});
};
// Application Role-------------------------------------------
export const useCreateApplicationRole = (onSuccessCallback) =>
{ {
const [ contactCategory, setContactCategory ] = useState( [] ) const queryClient = useQueryClient();
const [ loading, setLoading ] = useState( false )
const [ Error, setError ] = useState() return useMutation( {
mutationFn: async ( payload ) =>
const fetchConatctCategory = async() =>
{
const cache_Category = getCachedData( "Contact Category" );
if ( !cache_Category )
{ {
try const resp = await MasterRespository.createRole( payload );
{ return resp.data;
let resp = await MasterRespository.getContactCategory(); },
setContactCategory( resp.data ); onSuccess: ( data ) =>
cacheData("Contact Category",resp.data)
} catch ( error )
{
setError(error)
}
} else
{ {
setContactCategory(cache_Category) queryClient.invalidateQueries( {queryKey:[ "masterData", "Application Role" ]} )
showToast( "Application Role added successfully", "success" );
if(onSuccessCallback) onSuccessCallback(data)
},
onError: ( error ) =>
{
showToast(error.message || "Something went wrong", "error");
} }
} })
useEffect( () =>
{
fetchConatctCategory()
}, [] )
return { contactCategory,loading,Error}
} }
export const useContactTags = () => {
const [contactTags, setContactTags] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => { export const useUpdateApplicationRole = (onSuccessCallback) =>
const fetchContactTag = async () => { {
const cache_Tags = getCachedData("Contact Tag"); const queryClient = useQueryClient();
if (!cache_Tags) { return useMutation({
setLoading(true); mutationFn: async ( {id, payload} ) =>
try { {
const resp = await MasterRespository.getContactTag(); const response = await MasterRespository.updateRoles(id,payload);
setContactTags(resp.data); return response.data;
cacheData("Contact Tag", resp.data); },
} catch (err) { onSuccess: (data, variables) => {
setError(err);
} finally { queryClient.invalidateQueries({
setLoading(false); queryKey: ["masterData", "Application Role"],
} });
} else { showToast("Application Role updated successfully.", "success");
setContactTags(cache_Tags);
if (onSuccessCallback) onSuccessCallback(data);
},
onError: (error) => {
showToast(error.message || "Something went wrong", "error");
},
});
}
// Activity------------------------------
export const useCreateActivity = (onSuccessCallback) =>
{
const queryClient = useQueryClient();
return useMutation( {
mutationFn: async ( payload ) =>
{
const resp = await MasterRespository.createActivity(payload);
return resp.data;
},
onSuccess: ( data ) =>
{
queryClient.invalidateQueries( {queryKey:[ "masterData", "Activity" ]} )
showToast( "Activity added successfully", "success" );
if(onSuccessCallback) onSuccessCallback(data)
},
onError: ( error ) =>
{
showToast(error.message || "Something went wrong", "error");
}
})
}
export const useUpdateActivity = (onSuccessCallback) =>
{
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ( {id, payload} ) =>
{
const response = await MasterRespository.updateActivity(id,payload);
return response.data;
},
onSuccess: (data, variables) => {
queryClient.invalidateQueries({
queryKey: ["masterData", "Activity"],
});
showToast("Activity updated successfully.", "success");
if (onSuccessCallback) onSuccessCallback(data);
},
onError: (error) => {
showToast(error.message || "Something went wrong", "error");
},
});
}
//-----Create work Category-------------------------------
export const useCreateWorkCategory = (onSuccessCallback) =>
{
const queryClient = useQueryClient();
return useMutation( {
mutationFn: async ( payload ) =>
{
const resp = await MasterRespository.createWorkCategory(payload);
return resp.data;
},
onSuccess: ( data ) =>
{
queryClient.invalidateQueries({queryKey: [ "masterData", "Work Category" ]} )
showToast( "Work Category added successfully", "success" );
if(onSuccessCallback) onSuccessCallback(data)
},
onError: ( error ) =>
{
showToast(error.message || "Something went wrong", "error");
}
})
}
export const useUpdateWorkCategory = (onSuccessCallback) =>
{
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ( {id, payload} ) =>
{
const response = await MasterRespository.updateWorkCategory(id,payload);
return response.data;
},
onSuccess: (data, variables) => {
queryClient.invalidateQueries({
queryKey: ["masterData", "Work Category"],
});
showToast("Work Category updated successfully.", "success");
if (onSuccessCallback) onSuccessCallback(data);
},
onError: (error) => {
showToast(error.message || "Something went wrong", "error");
},
});
}
//-- Contact Category---------------------------
export const useCreateContactCategory = (onSuccessCallback) =>
{
const queryClient = useQueryClient();
return useMutation( {
mutationFn: async ( payload ) =>
{
const resp = await MasterRespository.createContactCategory(payload);
return resp.data;
},
onSuccess: ( data ) =>
{
queryClient.invalidateQueries( {queryKey:[ "masterData", "Contact Category" ]} )
showToast( "Contact Category added successfully", "success" );
if(onSuccessCallback) onSuccessCallback(data)
},
onError: ( error ) =>
{
showToast(error.message || "Something went wrong", "error");
}
})
}
export const useUpdateContactCategory = (onSuccessCallback) =>
{
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ( {id, payload} ) =>
{
const response = await MasterRespository.updateContactCategory(id,payload);
return response.data;
},
onSuccess: (data, variables) => {
queryClient.invalidateQueries({
queryKey: ["masterData", "Contact Category"],
});
showToast("Contact Category updated successfully.", "success");
if (onSuccessCallback) onSuccessCallback(data);
},
onError: (error) => {
showToast(error.message || "Something went wrong", "error");
},
});
}
// ---------Contact Tag-------------------
export const useCreateContactTag = (onSuccessCallback) =>
{
const queryClient = useQueryClient();
return useMutation( {
mutationFn: async ( payload ) =>
{
const resp = await MasterRespository.createContactTag(payload);
return resp.data;
},
onSuccess: ( data ) =>
{
queryClient.invalidateQueries( {queryKey:[ "masterData", "Contact Tag" ]} )
showToast( "Contact Tag added successfully", "success" );
if(onSuccessCallback) onSuccessCallback(data)
},
onError: ( error ) =>
{
showToast(error.message || "Something went wrong", "error");
}
})
}
export const useUpdateContactTag = (onSuccessCallback) =>
{
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ( {id, payload} ) =>
{
debugger
const response = await MasterRespository.updateContactTag(id,payload);
return response.data;
},
onSuccess: (data, variables) => {
queryClient.invalidateQueries({
queryKey: ["masterData", "Contact Tag"],
});
showToast("Contact Tag updated successfully.", "success");
if (onSuccessCallback) onSuccessCallback(data);
},
onError: (error) => {
showToast(error.message || "Something went wrong", "error");
},
});
}
// -Delete Master --------
export const useDeleteMasterItem = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ( {masterType, item} ) =>
{
const deleteFn = MasterRespository[masterType];
if (!deleteFn) {
throw new Error(`No delete strategy defined for master type: ${masterType}`);
} }
};
fetchContactTag(); await deleteFn(item.id);
}, []); return { masterType };
},
return { contactTags, loading, error }; onSuccess: ({ masterType }) => {
queryClient.invalidateQueries({ queryKey: ["masterData", masterType] });
showToast(`${masterType} deleted successfully.`, "success");
},
onError: (error) => {
const message =
error?.response?.data?.message || error?.message || "Error occurred during deletion";
showToast(message, "error");
},
});
}; };

View File

@ -3,236 +3,274 @@ import { cacheData, getCachedData } from "../slices/apiDataManager";
import { RolesRepository } from "../repositories/MastersRepository"; import { RolesRepository } from "../repositories/MastersRepository";
import EmployeeRepository from "../repositories/EmployeeRepository"; import EmployeeRepository from "../repositories/EmployeeRepository";
import ProjectRepository from "../repositories/ProjectRepository"; import ProjectRepository from "../repositories/ProjectRepository";
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import showToast from "../services/toastService";
import {useSelector} from "react-redux";
import {store} from "../store/store";
import {queryClient} from "../layouts/AuthLayout";
export const useAllEmployees = (showInactive) => {
const [employeesList, setEmployeeList] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const fetchData = async () => {
try {
let EmployeeList_cached = getCachedData("AllEmployees"); // Query ---------------------------------------------------------------------------
if (!EmployeeList_cached) {
setLoading(true);
const response = await EmployeeRepository.getAllEmployeeList(showInactive); export const useAllEmployees = ( showInactive ) =>
cacheData("AllEmployees", response.data); {
setEmployeeList(response.data); const {
setLoading(false); data = [],
} else { isLoading,
setEmployeeList(EmployeeList_cached); error,
setLoading(false); refetch, // optional if you want recall functionality
} } = useQuery({
} catch (error) { queryKey: ['allEmployee', showInactive],
setError("Failed to fetch data."); queryFn: async () => {
setLoading(false); const res = await EmployeeRepository.getAllEmployeeList(showInactive);
} return res.data;
},
});
return {
employeesList: data,
loading: isLoading,
error,
recallEmployeeData: refetch,
}; };
useEffect(() => {
fetchData();
}, []);
return { employeesList, loading, error };
}; };
export const useEmployees = (selectedProject) => { // ManageBucket.jsx
const [employees, setEmployeeList] = useState([]); export const useEmployees = ( selectedProject ) =>
const [loading, setLoading] = useState(true); {
const [projects, setProjects] = useState([]);
const fetchData = async (projectid) => { const {
try { data = [],
let EmployeeByProject_Cache = getCachedData("employeeListByProject"); isLoading,
if ( error,
!EmployeeByProject_Cache || refetch,
!EmployeeByProject_Cache.projectId === projectid } = useQuery({
) { queryKey: ["employeeListByProject", selectedProject],
EmployeeRepository.getEmployeeListByproject(projectid) queryFn: async () => {
.then((response) => { const res = await EmployeeRepository.getEmployeeListByproject(selectedProject);
setEmployeeList(response); return res.data || res;
cacheData("employeeListByProject", { },
data: response, enabled: !!selectedProject,
projectId: projectid, });
});
}) return {
.catch((error) => { employees: data,
setError("Failed to fetch data."); loading: isLoading,
}); projects: [], // if needed, pass this separately or manage from another hook
} else { reCallAllEmployee: refetch,
setEmployeeList(EmployeeByProject_Cache.data); error,
}
setLoading(false);
} catch (err) {
setError("Failed to fetch data.");
setLoading(false);
}
}; };
useEffect(() => {
if (selectedProject) {
fetchData(selectedProject);
}
}, [selectedProject]);
return { employees, loading, projects, reCallAllEmployee };
}; };
// ManageRole.jsx
export const useEmployeeRoles = (employeeId) => { export const useEmployeeRoles = (employeeId) => {
const [loading, setLoading] = useState(true); const {
const [error, setError] = useState(); data = [],
const [employeeRoles, setEmployeeRoles] = useState([]); isLoading: loading,
const fetchData = async (employeeid) => { error,
try { } = useQuery({
let response = await RolesRepository.getEmployeeRoles(employeeid); queryKey: ['employeeRoles', employeeId],
setEmployeeRoles(response.data); queryFn: async () => {
cacheData("employeelist", response.data); const res = await RolesRepository.getEmployeeRoles(employeeId);
} catch (err) { return res.data;
setError("Failed to fetch data."); },
setEmployeeRoles([]); enabled: !!employeeId,
} finally { });
setLoading(false);
} return {
employeeRoles: data,
loading,
error,
}; };
useEffect(() => {
if (employeeId) {
fetchData(employeeId);
}
}, [employeeId]);
return { employeeRoles, loading, error };
}; };
// EmployeeProfile.jsx
export const useEmployeesByProject = (projectId) => { export const useEmployeesByProject = (projectId) => {
const [loading, setLoading] = useState(false); const {
const [error, setError] = useState(); data = [],
const [employees, setEmployees] = useState([]); isLoading: loading,
error,
refetch: recallProjectEmplloyee,
} = useQuery({
queryKey: ['projectEmployees', projectId],
queryFn: async () => {
const res = await ProjectRepository.getEmployeesByProject(projectId);
return res.data;
},
enabled: !!projectId,
});
const fetchData = async () => { return {
const Employees_cache = getCachedData("employeeListByProject"); employees: data,
if (!Employees_cache || Employees_cache.projectId !== projectId) { loading,
setEmployees(true); error,
ProjectRepository.getEmployeesByProject(projectId) recallProjectEmplloyee,
.then((response) => {
setEmployees(response.data);
cacheData("employeeListByProject", {
data: response.data,
projectId,
});
setLoading(false);
})
.catch((error) => {
setError("Failed to fetch data.");
setLoading(false);
});
} else {
setEmployees(Employees_cache.data);
setLoading(false);
}
}; };
useEffect(() => {
fetchData(projectId);
}, [projectId]);
return { employees, loading, error, recallProjectEmplloyee: fetchData };
}; };
// EmployeeList.jsx
export const useEmployeesAllOrByProjectId = (projectId, showInactive) => { export const useEmployeesAllOrByProjectId = (projectId, showInactive) => {
const [employees, setEmployees] = useState([]); const isAllEmployees = !projectId && projectId !== undefined;
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchData = async (showInactive) => { const queryKey = isAllEmployees
if ( projectId ) ? ['allEmployees', showInactive]
{ : ['projectEmployees', projectId];
const Employees_cache = getCachedData("employeeListByProject");
if (!Employees_cache || Employees_cache.projectId !== projectId) {
setLoading(true);
setError(null);
try {
const response = await ProjectRepository.getEmployeesByProject(
projectId
);
setEmployees(response.data);
cacheData("employeeListByProject", {
data: response.data,
projectId,
});
setLoading(false);
} catch (err) {
setError("Failed to fetch data.");
setLoading(false);
}
} else {
setEmployees(Employees_cache.data);
setLoading(false);
}
} else
{
const cacheKey = showInactive
? "allInactiveEmployeeList"
: "allEmployeeList";
try { const queryFn = async () => {
const response = await EmployeeRepository.getAllEmployeeList( if (isAllEmployees) {
showInactive const res = await EmployeeRepository.getAllEmployeeList(showInactive);
); return res.data;
setEmployees(response.data); } else {
cacheData(cacheKey, { data: response.data }); const res = await ProjectRepository.getEmployeesByProject(projectId);
setLoading(false); return res.data;
} catch (err) {
setError("Failed to fetch data.");
setLoading(false);
}
} }
}; };
useEffect(() => { const {
fetchData(showInactive); // Fetch data when the component mounts or projectId changes data: employees = [],
}, [projectId]); // Re-fetch when projectId changes isLoading,
error,
refetch,
} = useQuery({
queryKey,
queryFn,
enabled: isAllEmployees || !!projectId,
});
return { return {
employees, employees,
loading: isLoading,
error,
recallEmployeeData: refetch,
};
};
// ManageEmployee.jsx
export const useEmployeeProfile = ( employeeId ) =>
{
const isEnabled = !!employeeId;
const {
data = null,
isLoading: loading,
error,
refetch
} = useQuery({
queryKey: ['employeeProfile', employeeId],
queryFn: async () => {
if (!employeeId) return null;
const res = await EmployeeRepository.getEmployeeProfile(employeeId);
return res.data;
},
enabled: isEnabled,
});
return {
employee: data,
loading, loading,
error, error,
recallEmployeeData: fetchData, refetch
}; };
}; };
export const useEmployeeProfile = (employeeId) => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState();
const [employee, setEmployees] = useState(null);
const fetchData = async () => { // Mutation------------------------------------------------------------------
if (!employeeId) {
// Reset the state if no employeeId (e.g., opening for 'add' mode)
setEmployees(null);
setLoading(false);
return;
}
const Employee_cache = getCachedData("employeeProfile");
if (!Employee_cache || Employee_cache.employeeId !== employeeId) {
EmployeeRepository.getEmployeeProfile(employeeId)
.then((response) => {
setEmployees(response.data);
cacheData("employeeProfile", { data: response.data, employeeId });
setLoading(false);
})
.catch((error) => {
setError("Failed to fetch data.");
setLoading(false);
});
} else {
setEmployees(Employee_cache.data);
setLoading(false);
}
};
useEffect(() => {
fetchData();
}, [employeeId]);
return { employee, loading, error };
export const useUpdateEmployee = () =>
{
const selectedProject = useSelector((store)=>store.localVariables.projectId)
const queryClient = useQueryClient();
return useMutation({
mutationFn: (employeeData) => EmployeeRepository.manageEmployee(employeeData),
onSuccess: (_, variables) => {
const id = variables.id || variables.employeeId;
const isAllEmployee = variables.IsAllEmployee;
// Cache invalidation
queryClient.invalidateQueries( {queryKey:[ 'allEmployees'] });
// queryClient.invalidateQueries(['employeeProfile', id]);
queryClient.invalidateQueries( {queryKey: [ 'projectEmployees' ]} );
queryClient.removeQueries( {queryKey: [ "empListByProjectAllocated" ]} );
// queryClient.invalidateQueries( {queryKey:[ 'employeeListByProject']} );
showToast( `Employee ${ id ? 'updated' : 'created' } successfully`, 'success' );
},
onError: (error) => {
const msg = error?.response?.data?.message || error.message || 'Something went wrong';
showToast(msg, 'error');
},
});
};
export const useSuspendEmployee = ({ setIsDeleteModalOpen, setemployeeLodaing }) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (id) => {
setemployeeLodaing(true);
return EmployeeRepository.deleteEmployee(id);
},
onSuccess: () => {
showToast("Employee deleted successfully.", "success");
// queryClient.invalidateQueries( ['allEmployee',false]);
queryClient.invalidateQueries( {queryKey: [ 'projectEmployees' ]} );
queryClient.invalidateQueries( {queryKey:[ 'employeeListByProject' ,selectedProject]} );
setIsDeleteModalOpen(false);
},
onError: (error) => {
const message =
error.response?.data?.message ||
error.message ||
"An unexpected error occurred";
showToast(message, "error");
setIsDeleteModalOpen(false);
},
onSettled: () => {
setemployeeLodaing(false);
},
});
};
// Manage Role
export const useUpdateEmployeeRoles = ({ onClose, resetForm, onSuccessCallback } = {}) => {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (updates) => RolesRepository.createEmployeeRoles(updates),
onSuccess: () => {
showToast("Roles updated successfully", "success");
resetForm?.();
onClose?.();
onSuccessCallback?.();
queryClient.invalidateQueries( {queryKey: [ "employeeRoles" ]} );
queryClient.invalidateQueries( {queryKey: [ "profile" ]} );
},
onError: (err) => {
const message =
err?.response?.data?.message || err?.message || "Error occurred while updating roles";
showToast(message, "error");
},
});
return {
updateRoles: mutation.mutate,
isPending: mutation.isPending,
isError: mutation.isError,
error: mutation.error,
};
}; };

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { cacheData,getCachedData } from "../slices/apiDataManager"; import { cacheData,getCachedData } from "../slices/apiDataManager";
import { MasterRespository } from "../repositories/MastersRepository"; import { MasterRespository } from "../repositories/MastersRepository";
import { useQuery } from "@tanstack/react-query";
export const useMasterRole =()=>{ export const useMasterRole =()=>{
@ -43,40 +43,55 @@ export const useMasterRole =()=>{
return {masterRole,loading} return {masterRole,loading}
} }
export const useFeatures =()=> { // export const useFeatures =()=> {
const [masterFeatures, setMasterFeatures] = useState([]); // const [masterFeatures, setMasterFeatures] = useState([]);
const [loading, setLoading] = useState(true); // const [loading, setLoading] = useState(true);
const [error, setError] = useState(""); // const [error, setError] = useState("");
const fetchData = async () => { // const fetchData = async () => {
try { // try {
const features_cache = getCachedData("masterFeatures"); // const features_cache = getCachedData("masterFeatures");
if (!features_cache) { // if (!features_cache) {
MasterRespository.getFeatures() // MasterRespository.getFeatures()
.then((response) => { // .then((response) => {
setMasterFeatures(response.data); // setMasterFeatures(response.data);
cacheData("features", response.data); // cacheData("features", response.data);
setLoading(false) // setLoading(false)
}) // })
.catch((error) => { // .catch((error) => {
setError("Failed to fetch data."); // setError("Failed to fetch data.");
}); // });
}else{ // }else{
if (!masterFeatures.length) setMasterFeatures(features_cache); // if (!masterFeatures.length) setMasterFeatures(features_cache);
} // }
} catch (err) { // } catch (err) {
setError("Failed to fetch data."); // setError("Failed to fetch data.");
} finally { // } finally {
setLoading(false); // setLoading(false);
} // }
}; // };
useEffect(()=>{ // useEffect(()=>{
fetchData(); // fetchData();
},[]) // },[])
return{masterFeatures,loading} // return{masterFeatures,loading}
} // }
// -----------------Query- -------------------------
export const useFeatures = () => {
const {data=[],isLoading,error} = useQuery({
queryKey: ["masterFeatures"],
queryFn: async () => {
const response = await MasterRespository.getFeatures();
return response.data;
},
} );
return {
masterFeatures:data,loading:isLoading,error
}
};

View File

@ -14,7 +14,7 @@ const usePagination = (data, itemsPerPage) => {
setCurrentPage(pageNumber); setCurrentPage(pageNumber);
}; };
return { currentPage, totalPages, currentItems, paginate }; return { currentPage, totalPages, currentItems, paginate,setCurrentPage };
}; };
export default usePagination; export default usePagination;

View File

@ -3,60 +3,99 @@ import AuthRepository from "../repositories/AuthRepository";
import {cacheData, cacheProfileData, getCachedData, getCachedProfileData} from "../slices/apiDataManager"; import {cacheData, cacheProfileData, getCachedData, getCachedProfileData} from "../slices/apiDataManager";
import {useSelector} from "react-redux"; import {useSelector} from "react-redux";
import eventBus from "../services/eventBus"; import eventBus from "../services/eventBus";
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
let hasFetched = false; let hasFetched = false;
let hasReceived = false; let hasReceived = false;
export const useProfile = () => { // export const useProfile = () => {
const loggedUser = useSelector( ( store ) => store.globalVariables.loginUser ); // const loggedUser = useSelector( ( store ) => store.globalVariables.loginUser );
const [profile, setProfile] = useState(null); // const [profile, setProfile] = useState(null);
const [loading, setLoading] = useState(false); // const [loading, setLoading] = useState(false);
const [error, setError] = useState(""); // const [error, setError] = useState("");
const fetchData = async () => { // const fetchData = async () => {
try { // try {
setLoading(true); // setLoading(true);
let response = await AuthRepository.profile(); // let response = await AuthRepository.profile();
setProfile(response.data); // setProfile(response.data);
cacheProfileData(response.data); // cacheProfileData(response.data);
} catch (error) { // } catch (error) {
setError("Failed to fetch data."); // setError("Failed to fetch data.");
} finally { // } finally {
setLoading(false); // setLoading(false);
} // }
}; // };
const validation = () => { // const validation = () => {
if (!hasFetched) { // if (!hasFetched) {
hasFetched = true; // hasFetched = true;
if (!loggedUser) { // if (!loggedUser) {
fetchData(); // fetchData();
} else { // } else {
setProfile(loggedUser); // setProfile(loggedUser);
} // }
} // }
setProfile(loggedUser); // setProfile(loggedUser);
} // }
useEffect(() => { // useEffect(() => {
validation(); // validation();
}, [loggedUser]); // }, [loggedUser]);
const handler = useCallback( // const handler = useCallback(
(data) => { // (data) => {
if(!getCachedData("hasReceived")){ // if(!getCachedData("hasReceived")){
cacheData("hasReceived", true); // cacheData("hasReceived", true);
hasFetched = false; // hasFetched = false;
validation(); // validation();
} // }
},[] // },[]
); // );
// useEffect(() => {
// eventBus.on("assign_project_one", handler);
// return () => eventBus.off("assign_project_one", handler);
// }, [handler]);
// return { profile, loading, error };
// };
export const useProfile = () => {
const loggedUser = useSelector((store) => store.globalVariables.loginUser);
const queryClient = useQueryClient();
const {
data: profile,
error,
isLoading,
refetch,
} = useQuery({
queryKey: ["profile"],
queryFn: async () => {
const response = await AuthRepository.profile();
cacheProfileData(response.data);
return response.data;
},
initialData: loggedUser || undefined,
enabled: !loggedUser,
staleTime: 10 * 60 * 1000,
});
const handler = useCallback(() => {
queryClient.invalidateQueries({ queryKey: ["profile"] });
}, [queryClient]);
useEffect(() => { useEffect(() => {
eventBus.on("assign_project_one", handler); eventBus.on("assign_project_one", handler);
return () => eventBus.off("assign_project_one", handler); return () => eventBus.off("assign_project_one", handler);
}, [handler]); }, [handler]);
return { profile, loading, error }; return {
profile,
loading: isLoading,
error,
refetch,
};
}; };

View File

@ -6,194 +6,538 @@ import { useDispatch, useSelector } from "react-redux";
import { setProjectId } from "../slices/localVariablesSlice"; import { setProjectId } from "../slices/localVariablesSlice";
import EmployeeList from "../components/Directory/EmployeeList"; import EmployeeList from "../components/Directory/EmployeeList";
import eventBus from "../services/eventBus"; import eventBus from "../services/eventBus";
import {Mutation, useMutation, useQuery, useQueryClient} from "@tanstack/react-query";
import showToast from "../services/toastService";
// export const useProjects = () => {
// const loggedUser = useSelector((store) => store.globalVariables.loginUser);
// const [projects, setProjects] = useState([]);
// const [loading, setLoading] = useState(true);
// const [error, setError] = useState("");
// const fetchData = async () => {
// const projectIds = loggedUser?.projects || [];
// const filterProjects = (projectsList) => {
// return projectsList
// .filter((proj) => projectIds.includes(String(proj.id)))
// .sort((a, b) => a?.name?.localeCompare(b.name));
// };
// const projects_cache = getCachedData("projectslist");
// if (!projects_cache) {
// setLoading(true);
// try {
// const response = await ProjectRepository.getProjectList();
// const allProjects = response.data;
// const filtered = filterProjects(allProjects);
// setProjects(filtered);
// cacheData("projectslist", allProjects);
// } catch (err) {
// setError("Failed to fetch data.");
// } finally {
// setLoading(false);
// }
// } else {
// if (!projects.length) {
// const filtered = filterProjects(projects_cache);
// setProjects(filtered);
// setLoading(false);
// }
// }
// };
// useEffect(() => {
// if (loggedUser) {
// fetchData();
// }
// }, [loggedUser]);
// return { projects, loading, error, refetch: fetchData };
// };
// export const useEmployeesByProjectAllocated = (selectedProject) => {
// const [projectEmployees, setEmployeeList] = useState([]);
// const [loading, setLoading] = useState(true);
// const [projects, setProjects] = useState([]);
// const fetchData = async (projectid) => {
// try {
// let EmployeeByProject_Cache = getCachedData("empListByProjectAllocated");
// if (
// !EmployeeByProject_Cache ||
// !EmployeeByProject_Cache.projectId === projectid
// ) {
// let response = await ProjectRepository.getProjectAllocation(projectid);
// setEmployeeList(response.data);
// cacheData("empListByProjectAllocated", {
// data: response.data,
// projectId: projectid,
// });
// setLoading(false);
// } else {
// setEmployeeList(EmployeeByProject_Cache.data);
// setLoading(false);
// }
// } catch (err) {
// setError("Failed to fetch data.");
// setLoading(false);
// }
// };
// useEffect(() => {
// if (selectedProject) {
// fetchData(selectedProject);
// }
// }, [selectedProject]);
// return { projectEmployees, loading, projects };
// };
// export const useProjectDetails = (projectId) => {
// const { profile } = useProfile();
// const [projects_Details, setProject_Details] = useState(null);
// const [loading, setLoading] = useState(true);
// const [error, setError] = useState("");
// const fetchData = async () => {
// setLoading(true);
// const project_cache = getCachedData("projectInfo");
// if (!project_cache || project_cache?.projectId != projectId) {
// ProjectRepository.getProjectByprojectId(projectId)
// .then((response) => {
// setProject_Details(response.data);
// cacheData("projectInfo", {
// projectId: projectId,
// data: response.data,
// });
// setLoading(false);
// })
// .catch((error) => {
// console.error(error);
// setError("Failed to fetch data.");
// setLoading(false);
// });
// } else {
// setProject_Details(project_cache.data);
// setLoading(false);
// }
// };
// useEffect(() => {
// if (profile && projectId != undefined) {
// fetchData();
// }
// }, [projectId, profile]);
// return { projects_Details, loading, error, refetch: fetchData };
// };
// export const useProjectsByEmployee = (employeeId) => {
// const [projectList, setProjectList] = useState([]);
// const [loading, setLoading] = useState(false);
// const [error, setError] = useState("");
// const fetchProjects = async (id) => {
// try {
// setLoading(true);
// setError(""); // clear previous error
// const res = await ProjectRepository.getProjectsByEmployee(id);
// setProjectList(res.data);
// cacheData("ProjectsByEmployee", { data: res.data, employeeId: id });
// setLoading(false);
// } catch (err) {
// setError(err?.message || "Failed to fetch projects");
// setLoading(false);
// }
// };
// useEffect(() => {
// if (!employeeId) return;
// const cache_project = getCachedData("ProjectsByEmployee");
// if (!cache_project?.data || cache_project?.employeeId !== employeeId) {
// fetchProjects(employeeId);
// } else {
// setProjectList(cache_project.data);
// }
// }, [employeeId]);
// return {
// projectList,
// loading,
// error,
// refetch: fetchProjects,
// };
// };
// export const useProjectName = () => {
// const [loading, setLoading] = useState(true);
// const [projectNames, setProjectName] = useState([]);
// const [Error, setError] = useState();
// const dispatch = useDispatch();
// const fetchData = async () => {
// try {
// let response = await ProjectRepository.projectNameList();
// setProjectName(response.data);
// cacheData("basicProjectNameList", response.data);
// setLoading(false);
// if(response.data.length === 1){
// dispatch(setProjectId(response.data[0]?.id));
// }
// } catch (err) {
// setError("Failed to fetch data.");
// setLoading(false);
// }
// };
// useEffect(() => {
// fetchData();
// }, []);
// return { projectNames, loading, Error, fetchData };
// };
// ------------------------------Query-------------------
export const useProjects = () => { export const useProjects = () => {
const loggedUser = useSelector((store) => store.globalVariables.loginUser); const loggedUser = useSelector((store) => store.globalVariables.loginUser);
const [projects, setProjects] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const fetchData = async () => { const {
const projectIds = loggedUser?.projects || []; data: projects = [],
isLoading: loading,
const filterProjects = (projectsList) => { error,
return projectsList refetch,
.filter((proj) => projectIds.includes(String(proj.id))) } = useQuery({
.sort((a, b) => a?.name?.localeCompare(b.name)); queryKey: ['ProjectsList'],
}; queryFn: async () => {
const response = await ProjectRepository.getProjectList();
const projects_cache = getCachedData("projectslist"); return response.data;
},
if (!projects_cache) { enabled: !!loggedUser,
setLoading(true); });
try {
const response = await ProjectRepository.getProjectList();
const allProjects = response.data;
const filtered = filterProjects(allProjects);
setProjects(filtered);
cacheData("projectslist", allProjects);
} catch (err) {
setError("Failed to fetch data.");
} finally {
setLoading(false);
}
} else {
if (!projects.length) {
const filtered = filterProjects(projects_cache);
setProjects(filtered);
setLoading(false);
}
}
};
useEffect(() => {
if (loggedUser) {
fetchData();
}
}, [loggedUser]);
return { projects, loading, error, refetch: fetchData };
};
export const useEmployeesByProjectAllocated = (selectedProject) => {
const [projectEmployees, setEmployeeList] = useState([]);
const [loading, setLoading] = useState(true);
const [projects, setProjects] = useState([]);
const fetchData = async (projectid) => {
try {
let EmployeeByProject_Cache = getCachedData("empListByProjectAllocated");
if (
!EmployeeByProject_Cache ||
!EmployeeByProject_Cache.projectId === projectid
) {
let response = await ProjectRepository.getProjectAllocation(projectid);
setEmployeeList(response.data);
cacheData("empListByProjectAllocated", {
data: response.data,
projectId: projectid,
});
setLoading(false);
} else {
setEmployeeList(EmployeeByProject_Cache.data);
setLoading(false);
}
} catch (err) {
setError("Failed to fetch data.");
setLoading(false);
}
};
useEffect(() => {
if (selectedProject) {
fetchData(selectedProject);
}
}, [selectedProject]);
return { projectEmployees, loading, projects };
};
export const useProjectDetails = (projectId) => {
const { profile } = useProfile();
const [projects_Details, setProject_Details] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const fetchData = async () => {
setLoading(true);
const project_cache = getCachedData("projectInfo");
if (!project_cache || project_cache?.projectId != projectId) {
ProjectRepository.getProjectByprojectId(projectId)
.then((response) => {
setProject_Details(response.data);
cacheData("projectInfo", {
projectId: projectId,
data: response.data,
});
setLoading(false);
})
.catch((error) => {
console.error(error);
setError("Failed to fetch data.");
setLoading(false);
});
} else {
setProject_Details(project_cache.data);
setLoading(false);
}
};
useEffect(() => {
if (profile && projectId != undefined) {
fetchData();
}
}, [projectId, profile]);
return { projects_Details, loading, error, refetch: fetchData };
};
export const useProjectsByEmployee = (employeeId) => {
const [projectList, setProjectList] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const fetchProjects = async (id) => {
try {
setLoading(true);
setError(""); // clear previous error
const res = await ProjectRepository.getProjectsByEmployee(id);
setProjectList(res.data);
cacheData("ProjectsByEmployee", { data: res.data, employeeId: id });
setLoading(false);
} catch (err) {
setError(err?.message || "Failed to fetch projects");
setLoading(false);
}
};
useEffect(() => {
if (!employeeId) return;
const cache_project = getCachedData("ProjectsByEmployee");
if (!cache_project?.data || cache_project?.employeeId !== employeeId) {
fetchProjects(employeeId);
} else {
setProjectList(cache_project.data);
}
}, [employeeId]);
return { return {
projectList, projects,
loading, loading,
error, error,
refetch: fetchProjects, refetch,
}; };
}; };
export const useProjectName = () => { export const useEmployeesByProjectAllocated = (selectedProject) =>
const [loading, setLoading] = useState(true); {
const [projectNames, setProjectName] = useState([]); const {data = [], isLoading, refetch, error} = useQuery( {
const [Error, setError] = useState(); queryKey: ["empListByProjectAllocated", selectedProject ],
const dispatch = useDispatch(); queryFn: async () =>
{
const fetchData = async () => { const res = await ProjectRepository.getProjectAllocation( selectedProject );
try { return res.data || res
let response = await ProjectRepository.projectNameList(); },
setProjectName(response.data); enabled: !!selectedProject,
cacheData("basicProjectNameList", response.data); onError: ( error ) =>
setLoading(false); {
if(response.data.length === 1){ showToast(error.message || "Error while Fetching project Allocated Employees", "error");
dispatch(setProjectId(response.data[0]?.id));
}
} catch (err) {
setError("Failed to fetch data.");
setLoading(false);
} }
}; } )
useEffect(() => {
fetchData(); return {
}, []); projectEmployees: data,
loading:isLoading,
error,
refetch
}
}
return { projectNames, loading, Error, fetchData }; export const useProjectDetails = ( projectId,isAuto = true ) =>
{
const {data: projects_Details, isLoading, error, refetch} = useQuery( {
queryKey: [ "projectInfo", projectId ],
queryFn: async () =>
{
const res = await ProjectRepository.getProjectByprojectId( projectId );
return res.data || res;
},
enabled: !!projectId && isAuto,
onError: ( error ) =>
{
showToast(error.message || "Error while Fetching project Details", "error");
}
} )
return { projects_Details, loading:isLoading, error, refetch };
}
export const useProjectsByEmployee = (employeeId) =>
{
const { data:projectNameList =[],isLoading,error,refetch} = useQuery( {
queryKey: [ "ProjectsByEmployee", employeeId ],
queryFn: async () =>
{
const res = await ProjectRepository.getProjectsByEmployee( employeeId );
return res.data || res;
},
enabled: !!employeeId,
onError: ( error ) =>
{
showToast(error.message || "Error while Fetching project Employee", "error");
}
})
return {projectList, loading:isLoading,error,refetch }
}
export const useProjectName = () =>
{
const {data = [],isLoading,error,refetch} = useQuery( {
queryKey: [ "basicProjectNameList" ],
queryFn: async () =>
{
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-------------------------------
export const useCreateProject = ({ onSuccessCallback }) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (newProject) => {
const res = await ProjectRepository.manageProject(newProject);
return res.data;
},
onSuccess: (data) => {
// Invalidate the cache
queryClient.invalidateQueries( {queryKey: [ 'ProjectsList' ]} );
queryClient.invalidateQueries({queryKey:['basicProjectNameList']});
// Emit event for consumers (like useProjects or others)
eventBus.emit("project", {
keyword: "Create_Project",
response: data,
});
showToast("Project Created successfully.", "success");
if (onSuccessCallback) {
onSuccessCallback(data);
}
},
onError: (error) => {
showToast(error.message || "Error while creating project", "error");
},
});
};
export const useUpdateProject = ({ onSuccessCallback }) => {
const queryClient = useQueryClient();
const {
mutate,
isPending,
isSuccess,
isError,
} = useMutation({
mutationFn: async ( {projectId, updatedData} ) =>
{
return await ProjectRepository.updateProject(projectId, updatedData);
},
onSuccess: ( data, variables ) =>
{
const { projectId } = variables;
queryClient.invalidateQueries({queryKey:["ProjectsList"]});
queryClient.invalidateQueries( {queryKey: [ "projectInfo", projectId ]} );
queryClient.invalidateQueries({queryKey:['basicProjectNameList']});
eventBus.emit("project", {
keyword: "Update_Project",
response: data,
});
showToast("Project updated successfully.", "success");
if (onSuccessCallback) {
onSuccessCallback(data);
}
},
onError: ( error ) =>
{
console.log(error)
showToast(error?.message || "Error while updating project", "error");
},
});
return {
mutate,
isPending,
isSuccess,
isError,
};
};
export const useManageProjectInfra = ( {onSuccessCallback} ) =>
{
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ( {infraObject, projectId} ) =>
{
return await ProjectRepository.manageProjectInfra(infraObject);
},
onSuccess: ( data, variables ) =>
{
const { projectId } = variables;
queryClient.invalidateQueries({queryKey:["ProjectInfra", projectId]});
if (onSuccessCallback) onSuccessCallback(data,variables);
},
onError: (error) => {
showToast(error.message || "Failed to update Project Infra", "error");
},
});
};
export const useManageProjectAllocation = ({
onSuccessCallback,
onErrorCallback,
}) => {
const queryClient = useQueryClient();
const {
mutate,
isPending,
isSuccess,
isError,
} = useMutation({
mutationFn: async ( {items} ) =>
{
const response = await ProjectRepository.manageProjectAllocation(items);
return response.data;
},
onSuccess: (data, variables, context) => {
queryClient.invalidateQueries({queryKey:['empListByProjectAllocated']});
queryClient.removeQueries({queryKey:["projectEmployees"]})
if (variables?.added) {
showToast('Employee Assigned Successfully', 'success');
} else {
showToast('Removed Employee Successfully', 'success');
}
if (onSuccessCallback) onSuccessCallback(data, context);
},
onError: (error) => {
const message =
error?.response?.data?.message || error.message || 'Error occurred during API call';
showToast(message, 'error');
if (onErrorCallback) onErrorCallback(error);
},
});
return {
mutate,
isPending,
isSuccess,
isError,
};
};
export const useManageTask = ({onSuccessCallback}) =>
{
const queryClient = useQueryClient();
return useMutation( {
mutationFn: async ( payload ) => await ProjectRepository.manageProjectTasks( payload ),
onSuccess: ( data, variables ) =>
{
queryClient.invalidateQueries({ queryKey: ["WorkItems"] })
if (onSuccessCallback) onSuccessCallback(data);
},
onError: (error) =>
{
const message =
error?.response?.data?.message || error.message || 'Error occurred during API call';
showToast(message, 'error');
}
})
}
export const useDeleteProjectTask = (onSuccessCallback) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ( {workItemId, workAreaId} ) =>
{
return await ProjectRepository.deleteProjectTask(workItemId);
},
onSuccess: ( _, variables ) =>
{
showToast("Task deleted successfully", "success");
queryClient.invalidateQueries({queryKey:[ "WorkItems",variables.workAreaId]});
if (onSuccessCallback) onSuccessCallback();
},
onError: (error) => {
showToast(
error?.response?.data?.message || error.message || "Failed to delete task",
"error"
);
if (onSuccessCallback) onSuccessCallback();
},
});
};

View File

@ -2,103 +2,192 @@ import { useEffect, useState } from "react";
import { TasksRepository } from "../repositories/TaskRepository"; import { TasksRepository } from "../repositories/TaskRepository";
import { cacheData, getCachedData } from "../slices/apiDataManager"; import { cacheData, getCachedData } from "../slices/apiDataManager";
import {MasterRespository} from "../repositories/MastersRepository"; import {MasterRespository} from "../repositories/MastersRepository";
// import {formatDate} from "../utils/dateUtils"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import showToast from "../services/toastService";
import {useSelector} from "react-redux";
// ---------Query---------------------------------
export const useTaskList = (projectId, dateFrom, toDate) => { export const useTaskList = (projectId, dateFrom, toDate) => {
const [TaskList, setTaskList] = useState([]); const enabled = !!projectId && !!dateFrom && !!toDate;
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchList = async (projectId, dateFrom, toDate) => { const {
const taskList_cached = getCachedData("taskList"); data: TaskList = [],
// if (!taskList_cached || taskList_cached?.projectId !== projectId) { isLoading: loading,
try { error,
setLoading(true); refetch,
const resp = await TasksRepository.getTaskList( } = useQuery({
projectId, queryKey: ["taskList", projectId, dateFrom, toDate],
dateFrom, queryFn: async () => {
toDate const response = await TasksRepository.getTaskList(
); projectId,
setTaskList(resp.data); dateFrom,
cacheData("taskList", { projectId: projectId, data: resp.data }); toDate
setLoading(false); );
} catch (err) { return response.data;
setLoading(false); },
setError(err); enabled,
}
// } else { });
// setTaskList(taskList_cached.data);
// }
};
useEffect( () =>
{
if (projectId && dateFrom && toDate) { return { TaskList, loading, error, refetch };
fetchList(projectId, dateFrom, toDate); };
}
}, [projectId, dateFrom, toDate]);
return { TaskList, loading, error, refetch:fetchList}; export const useTaskById = (TaskId) => {
const {
data: Task = null,
isLoading: loading,
error,
refetch,
} = useQuery({
queryKey: ["taskDetails", TaskId],
queryFn: async () => {
const res = await TasksRepository.getTaskById(TaskId);
return res.data;
},
enabled: !!TaskId,
});
return { Task, loading, error, refetch };
};
// export const useActivities = () => {
// return useQuery({
// queryKey: ["activitiesMaster"],
// queryFn: async () => {
// const response = await ActivityRepository.getActivities();
// return response.data;
// },
// });
// };
export const useAuditStatus = () => {
const {
data: status = [],
isLoading: loading,
error,
refetch,
} = useQuery({
queryKey: ["AuditStatus"],
queryFn: async () => {
const res = await MasterRespository.getAuditStatus();
return res.data;
},
});
return { status, loading, error, refetch };
}; };
export const useTaskById = (TaskId) => // -----------------------Mutation------------------------
const toDate = new Date().toISOString().split('T')[0];
const dateFrom = new Date(Date.now() - 6 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
export const useReportTask = ( {onSuccessCallback, onErrorCallback} = {} ) =>
{ {
const [Task, setTask] = useState([]); const queryClient = useQueryClient();
const [loading, setLoading] = useState(false); const {
const [ error, setError ] = useState( null ); mutate,
isPending,
isSuccess,
isError,
const fetchTask = async(TaskId) => error,
{ } = useMutation({
try mutationFn: async ( {reportData,workAreaId} ) =>
{ {
let res = await TasksRepository.getTaskById( TaskId ); debugger
setTask( res.data ); return await TasksRepository.reportTask(reportData);
},
} catch ( error ) onSuccess: ( data, variables ) =>
{ {
setError(err) const {workAreaId} = variables;
} queryClient.invalidateQueries( {queryKey: [ "taskList" ]} );
} queryClient.invalidateQueries( {queryKey: [ "WorkItems", workAreaId ]} );
useEffect( () => queryClient.invalidateQueries( {queryKey: [ 'ProjectsList' ]} );
{ showToast( "Task Reported Successfully.", "success" );
if ( TaskId ) if (onSuccessCallback) onSuccessCallback(data);
{ },
fetchTask(TaskId) onError: (error) => {
} const msg =
}, [ TaskId ] ) error?.response?.data?.message || error.message || "Error occurred during API call";
return { Task,loading} showToast( msg, "error" );
} },
});
export const useAuditStatus = () => return {
mutate,
isPending,
isSuccess,
isError,
error,
};
};
export const useSubmitTaskComment = ({ actionAllow, onSuccessCallback }) => {
const queryClient = useQueryClient();
const { mutate, isPending } = useMutation({
mutationFn: async ({ data, commentsData }) => {
const payload = {
...data,
[actionAllow ? "id" : "taskAllocationId"]: commentsData?.id,
...(actionAllow ? {} : { commentDate: new Date().toISOString() }),
};
const response = actionAllow
? await TasksRepository.auditTask(payload)
: await TasksRepository.taskComments(payload);
return response.data;
},
onSuccess: ( data,variables ) =>
{
const workAreaId = variables?.commentsData?.workItem?.workArea?.id;
queryClient.invalidateQueries({ queryKey: ["taskList"] });
if (actionAllow) {
showToast( "Review submitted successfully.", "success" );
} else
{
showToast("Comment sent successfully.", "success");
}
onSuccessCallback?.(data);
},
onError: (error) => {
const msg = error?.response?.data?.message || error.message || "Error during API call";
showToast(msg, "error");
},
});
return { submitComment: mutate, isPending };
};
export const useCreateTask = ( {onSuccessCallback, onErrorCallback} = {} ) =>
{ {
const [ status, setStatus ] = useState( [] ); const queryClient = useQueryClient();
const [ error, setError ] = useState( '' ); return useMutation({
const [ loading, setLoading ] = useState( false ) mutationFn: async ({payload,workAreaId}) => {
return await TasksRepository.assignTask(payload);
const fetchStatus = async() => },
{ onSuccess: ( _, variables ) =>
try
{ {
const res = await MasterRespository.getAuditStatus() queryClient.invalidateQueries( {queryKey: [ "taskList" ]} );
setStatus( res.data ) queryClient.invalidateQueries( {queryKey: [ "WorkItems", variables?.workAreaId ]} );
cacheData("AuditStatus",res.data) showToast( "Task Assigned Successfully.", "success" );
} catch ( err ) if (onSuccessCallback) onSuccessCallback(variables);
},
onError: ( error ) =>
{ {
setError(err) showToast("Something went wrong. Please try again.", "error");
} if (onErrorCallback) onErrorCallback(error);
} },
useEffect(() => { });
const cache_status = getCachedData('AuditStatus'); };
if (cache_status) {
setStatus(cache_status);
} else {
fetchStatus();
}
}, []);
return {status,error,loading}
}

View File

@ -1,5 +1,17 @@
import React from "react"; import React from "react";
import { Outlet } from "react-router-dom"; import {Outlet} from "react-router-dom";
import {QueryClient} from '@tanstack/react-query';
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 min: data considered fresh
refetchOnWindowFocus: true, // refresh on tab switch
refetchOnReconnect: true, // re-fetch if network was lost
retry: false,
},
},
});
const AuthLayout = () => { const AuthLayout = () => {
return ( return (

View File

@ -9,10 +9,11 @@ import ReportTaskComments from "../../components/Activities/ReportTaskComments";
import DateRangePicker from "../../components/common/DateRangePicker"; import DateRangePicker from "../../components/common/DateRangePicker";
import { useSearchParams } from "react-router-dom"; import { useSearchParams } from "react-router-dom";
import moment from "moment"; import moment from "moment";
import FilterIcon from "../../components/common/FilterIcon"; // Import the FilterIcon component import FilterIcon from "../../components/common/FilterIcon";
import GlobalModel from "../../components/common/GlobalModel"; import GlobalModel from "../../components/common/GlobalModel";
import AssignTask from "../../components/Project/AssignTask"; import AssignTask from "../../components/Project/AssignTask";
import SubTask from "../../components/Activities/SubTask"; import SubTask from "../../components/Activities/SubTask";
import {formatNumber} from "../../utils/dateUtils";
const DailyTask = () => { const DailyTask = () => {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
@ -20,14 +21,7 @@ const DailyTask = () => {
const selectedProject = useSelector( const selectedProject = useSelector(
(store) => store.localVariables.projectId (store) => store.localVariables.projectId
); );
const {
projects,
loading: project_loading,
error: projects_Error,
} = useProjects();
const [initialized, setInitialized] = useState(false);
const dispatch = useDispatch();
const [filters, setFilters] = useState({ const [filters, setFilters] = useState({
selectedBuilding: "", selectedBuilding: "",
@ -35,23 +29,7 @@ const DailyTask = () => {
selectedActivities: [], selectedActivities: [],
}); });
useEffect(() => {
if (!project_loading && projects.length > 0 && !initialized) {
if (projectIdFromUrl) {
dispatch(setProjectId(projectIdFromUrl));
} else if (selectedProject === 1 || selectedProject === undefined) {
dispatch(setProjectId(projects[0].id));
}
setInitialized(true);
}
}, [
project_loading,
projects,
projectIdFromUrl,
selectedProject,
initialized,
dispatch,
]);
const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" }); const [dateRange, setDateRange] = useState({ startDate: "", endDate: "" });
@ -61,10 +39,11 @@ const DailyTask = () => {
error: task_error, error: task_error,
refetch, refetch,
} = useTaskList( } = useTaskList(
initialized ? selectedProject : null, selectedProject || null,
initialized ? dateRange.startDate : null, dateRange?.startDate || null,
initialized ? dateRange.endDate : null dateRange?.endDate || null
); );
const [TaskLists, setTaskLists] = useState([]); const [TaskLists, setTaskLists] = useState([]);
const [dates, setDates] = useState([]); const [dates, setDates] = useState([]);
@ -83,8 +62,8 @@ const DailyTask = () => {
} }
if (filters.selectedFloors.length > 0) { if (filters.selectedFloors.length > 0) {
filteredTasks = filteredTasks.filter((task) => filteredTasks = filteredTasks?.filter((task) =>
filters.selectedFloors.includes( filters.selectedFloors?.includes(
task?.workItem?.workArea?.floor?.floorName task?.workItem?.workArea?.floor?.floorName
) )
); );
@ -103,9 +82,9 @@ const DailyTask = () => {
} }
}, [ }, [
TaskList, TaskList,
filters.selectedBuilding, filters?.selectedBuilding,
filters.selectedFloors, filters?.selectedFloors,
filters.selectedActivities, filters?.selectedActivities,
]); ]);
useEffect(() => { useEffect(() => {
@ -150,43 +129,32 @@ const DailyTask = () => {
const handlecloseModal = () => const handlecloseModal = () =>
{ {
setIsModalOpen( false ) setIsModalOpen( false )
refetch(selectedProject, dateRange.startDate, dateRange.endDate); // refetch();
} }
const handleProjectChange = (e) => {
const newProjectId = e.target.value;
dispatch(setProjectId(newProjectId));
setTaskLists([]);
setFilters({
selectedBuilding: "",
selectedFloors: [],
selectedActivities: [],
});
};
const handleCloseAction = (IsSubTask) => { const handleCloseAction = (IsSubTask) => {
if (IsSubTask) { if (IsSubTask) {
setIsSubTaskNeeded(true); setIsSubTaskNeeded(true);
setIsModalOpenComment(false); setIsModalOpenComment(false);
} else { } else {
refetch(selectedProject, dateRange.startDate, dateRange.endDate); // refetch();
setIsModalOpenComment(false); setIsModalOpenComment(false);
} }
}; };
const hanleCloseSubTask = () => { const hanleCloseSubTask = () => {
setIsSubTaskNeeded(false); setIsSubTaskNeeded(false);
setComment( null ); setComment( null );
refetch(selectedProject, dateRange.startDate, dateRange.endDate); // refetch();
}; };
return ( return (
<> <>
{isModalOpen && <GlobalModel isOpen={isModalOpen} size="md" closeModal={handlecloseModal} > {isModalOpen && <GlobalModel isOpen={isModalOpen} size="md" closeModal={handlecloseModal} >
<ReportTask <ReportTask
report={selectedTask} report={selectedTask}
closeModal={handlecloseModal} closeModal={handlecloseModal}
refetch={refetch} // refetch={refetch}
/> />
</GlobalModel>} </GlobalModel>}
@ -255,7 +223,7 @@ const DailyTask = () => {
</thead> </thead>
<tbody className="table-border-bottom-0"> <tbody className="table-border-bottom-0">
{/* --- Spinner when tasks are loading --- */} {/* --- Spinner when tasks are loading --- */}
{(task_loading || project_loading) && ( {task_loading && (
<tr> <tr>
<td colSpan={6} className="text-center"> <td colSpan={6} className="text-center">
{" "} {" "}
@ -272,7 +240,6 @@ const DailyTask = () => {
</tr> </tr>
)} )}
{!task_loading && {!task_loading &&
!project_loading &&
TaskLists.length === 0 && ( TaskLists.length === 0 && (
<tr> <tr>
<td colSpan={6} className="text-center"> <td colSpan={6} className="text-center">
@ -337,9 +304,7 @@ const DailyTask = () => {
</div> </div>
</td> </td>
<td> <td>
{task.plannedTask || "NA"} / {formatNumber(task.plannedTask)} / {formatNumber(task.workItem.plannedWork - task.workItem.completedWork)}
{task.workItem.plannedWork -
task.workItem.completedWork}
</td> </td>
<td>{task.completedTask}</td> <td>{task.completedTask}</td>
<td> <td>
@ -413,7 +378,7 @@ const DailyTask = () => {
} more`} } more`}
> >
<span className="avatar-initial rounded-circle bg-label-secondary pull-up"> <span className="avatar-initial rounded-circle bg-label-secondary pull-up">
+{task.teamMembers.length - 3} + {task.teamMembers.length - 3}
</span> </span>
</div> </div>
)} )}

View File

@ -1,110 +1,9 @@
import React, { useState, useEffect } from "react"; import React from "react";
import "../../components/Project/ProjectInfra.css";
import ProjectRepository from "../../repositories/ProjectRepository";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import InfraPlanning from "../../components/Activities/InfraPlanning"; import InfraPlanning from "../../components/Activities/InfraPlanning";
import { cacheData, getCachedData } from "../../slices/apiDataManager";
import { useProfile } from "../../hooks/useProfile";
import { useDispatch, useSelector } from "react-redux";
import { useProjectDetails, useProjects } from "../../hooks/useProjects";
import { setProjectId } from "../../slices/localVariablesSlice";
import showToast from "../../services/toastService";
const TaskPlannng = () => { const TaskPlannng = () => {
const { profile } = useProfile();
const {
projects,
loading: project_listLoader,
error: projects_error,
} = useProjects();
const dispatch = useDispatch();
const selectedProject = useSelector(
(store) => store.localVariables.projectId
);
const [project, setProject] = useState(null);
const [projectDetails, setProjectDetails] = useState(null);
const [activities, setActivities] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const fetchActivities = async () => {
try {
const activities_cache = getCachedData("activitiesMaster");
if (!activities_cache) {
ActivityeRepository.getActivities()
.then((response) => {
setActivities(response.data);
cacheData("activitiesMaster", response.data);
})
.catch((error) => {
setError("Failed to fetch data.");
});
} else {
setActivities(activities_cache);
}
} catch (err) {
setError("Failed to fetch activities.");
} finally {
// setLoading(false);
}
};
const fetchData = async () => {
try {
const project_cache = getCachedData("projectInfo");
if (!project_cache || !project_cache.projectId == selectedProject) {
ProjectRepository.getProjectByprojectId(selectedProject)
.then((response) => {
setProjectDetails(response);
setProject(response);
cacheData("projectInfo", {
data: response.data,
projectId: selectedProject,
});
})
.catch((error) => {
const message =
error.response?.data?.message ||
error.message ||
"An unexpected error occurred";
showToast( message, "error" );
});
} else {
setProjectDetails(project_cache);
}
} catch (err) {
setError( "Failed to fetch data." );
const message =
error.response?.data?.message ||
error.message ||
"An unexpected error occurred";
showToast( message, "error" );
} finally {
setLoading(false);
}
};
const [activePill, setActivePill] = useState("profile");
const handlePillClick = (pillKey) => {
setActivePill(pillKey);
};
const handleDataChange = (data) => {
fetchData();
};
useEffect(() => {
if (projects.length != 0 && selectedProject) {
fetchData();
fetchActivities();
}
}, [selectedProject]);
return ( return (
<> <>
@ -115,17 +14,7 @@ const TaskPlannng = () => {
{ label: "Daily Task Planning" } { label: "Daily Task Planning" }
]} ]}
></Breadcrumb> ></Breadcrumb>
{project_listLoader && <p>Loading..</p>} <InfraPlanning/>
{!project_listLoader && projects.length === 0 && (
<p>No Project Found.</p>
)}
{!project_listLoader && projects.length > 0 && (
<InfraPlanning
data={projectDetails}
activityMaster={activities}
onDataChange={handleDataChange}
/>
)}
</div> </div>
</> </>
); );

View File

@ -5,10 +5,10 @@ import { Link, NavLink, useNavigate } from "react-router-dom";
import Avatar from "../../components/common/Avatar"; import Avatar from "../../components/common/Avatar";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import ManageEmp from "../../components/Employee/ManageRole"; import ManageEmp from "../../components/Employee/ManageRole";
import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees"; import { useEmployeesAllOrByProjectId, useSuspendEmployee } from "../../hooks/useEmployees";
import { useProjects } from "../../hooks/useProjects"; // Keep if you use projects elsewhere import { useProjects } from "../../hooks/useProjects";
import { useProfile } from "../../hooks/useProfile"; // Keep if you use profile elsewhere import { useProfile } from "../../hooks/useProfile";
import { hasUserPermission } from "../../utils/authUtils"; // Keep if you use this elsewhere import { hasUserPermission } from "../../utils/authUtils";
import { ITEMS_PER_PAGE, MANAGE_EMPLOYEES } from "../../utils/constants"; import { ITEMS_PER_PAGE, MANAGE_EMPLOYEES } from "../../utils/constants";
import { clearCacheKey } from "../../slices/apiDataManager"; import { clearCacheKey } from "../../slices/apiDataManager";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
@ -26,6 +26,7 @@ import { useSelector } from "react-redux";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import { newlineChars } from "pdf-lib"; import { newlineChars } from "pdf-lib";
import GlobalModel from "../../components/common/GlobalModel"; import GlobalModel from "../../components/common/GlobalModel";
import usePagination from "../../hooks/usePagination";
const EmployeeList = () => { const EmployeeList = () => {
const selectedProjectId = useSelector( const selectedProjectId = useSelector(
@ -38,14 +39,15 @@ const EmployeeList = () => {
const { employees, loading, setLoading, error, recallEmployeeData } = const { employees, loading, setLoading, error, recallEmployeeData } =
useEmployeesAllOrByProjectId( useEmployeesAllOrByProjectId(
showAllEmployees ? null : selectedProjectId, // Use selectedProjectId here showAllEmployees ? null : selectedProjectId,
showInactive showInactive
); );
const [employeeList, setEmployeeList] = useState([]); const [employeeList, setEmployeeList] = useState([]);
const [modelConfig, setModelConfig] = useState(); const [ modelConfig, setModelConfig ] = useState();
const [currentPage, setCurrentPage] = useState(1); const [EmpForManageRole,setEmpForManageRole] = useState(null)
const [itemsPerPage] = useState(ITEMS_PER_PAGE); // const [currentPage, setCurrentPage] = useState(1);
// const [itemsPerPage] = useState(ITEMS_PER_PAGE);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const [filteredData, setFilteredData] = useState([]); const [filteredData, setFilteredData] = useState([]);
@ -53,135 +55,131 @@ const EmployeeList = () => {
const [selectedEmployeeId, setSelecedEmployeeId] = useState(null); const [selectedEmployeeId, setSelecedEmployeeId] = useState(null);
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [selectedEmpFordelete, setSelectedEmpFordelete] = useState(null); const [selectedEmpFordelete, setSelectedEmpFordelete] = useState(null);
const [employeeLodaing, setemployeeLodaing] = useState(false); const [ employeeLodaing, setemployeeLodaing ] = useState( false );
const {
mutate: suspendEmployee,
isPending: empLodaing
} = useSuspendEmployee({
setIsDeleteModalOpen,
setemployeeLodaing
} );
const navigate = useNavigate(); const navigate = useNavigate();
/**
* Applies the search filter to a given array of employee data.
* @param {Array} data - The array of employee objects to filter.
* @param {string} text - The search text.
* @returns {Array} The filtered array.
*/
const applySearchFilter = (data, text) => { const applySearchFilter = (data, text) => {
if (!text) { if (!text) {
return data; return data;
} }
const lowercasedText = text.toLowerCase().trim(); // Ensure search text is trimmed and lowercase
return data.filter((item) => { const lowercasedText = text.toLowerCase().trim();
// **IMPROVED FULL NAME CONSTRUCTION**
const firstName = item.firstName || "";
const middleName = item.middleName || "";
const lastName = item.lastName || "";
// Join parts, then trim any excess spaces if a middle name is missing return data.filter((item) => {
const fullName = `${firstName} ${middleName} ${lastName}`.toLowerCase().trim().replace(/\s+/g, ' '); const firstName = item.firstName || "";
const middleName = item.middleName || "";
const lastName = item.lastName || "";
const email = item.email ? item.email.toLowerCase() : ""; const fullName = `${firstName} ${middleName} ${lastName}`
const phoneNumber = item.phoneNumber ? item.phoneNumber.toLowerCase() : ""; .toLowerCase()
const jobRole = item.jobRole ? item.jobRole.toLowerCase() : ""; .trim()
.replace(/\s+/g, " ");
const email = item.email?.toLowerCase() || "";
const phoneNumber = item.phoneNumber?.toLowerCase() || "";
const jobRole = item.jobRole?.toLowerCase() || "";
return (
fullName.includes(lowercasedText) ||
email.includes(lowercasedText) ||
phoneNumber.includes(lowercasedText) ||
jobRole.includes(lowercasedText)
);
});
};
return (
fullName.includes(lowercasedText) ||
email.includes(lowercasedText) ||
phoneNumber.includes(lowercasedText) ||
jobRole.includes(lowercasedText)
);
});
};
const handleSearch = (e) => { const handleSearch = (e) => {
const value = e.target.value; const value = e.target.value;
setSearchText(value); setSearchText(value);
setCurrentPage(1); setCurrentPage(1);
}; };
useEffect(() => {
const filtered = applySearchFilter(employeeList, searchText);
setFilteredData(filtered);
}, [searchText, employeeList]);
useEffect(() => {
setCurrentPage(1);
if (!loading && Array.isArray(employees)) {
const sorted = [...employees].sort((a, b) => {
const nameA = `${a.firstName || ""}${a.middleName || ""}${a.lastName || ""}`.toLowerCase();
const nameB = `${b.firstName || ""}${b.middleName || ""}${b.lastName || ""}`.toLowerCase();
return nameA?.localeCompare(nameB);
});
setEmployeeList(sorted); const displayData = searchText ? filteredData : employeeList;
const results = applySearchFilter(sorted, searchText); const { currentPage, totalPages, currentItems, paginate,setCurrentPage } = usePagination(
setFilteredData(results); displayData,
} else if (!loading && !employees) { ITEMS_PER_PAGE
setEmployeeList([]); );
setFilteredData([]); const openModal = () => {
}
}, [loading, employees, showAllEmployees, searchText, selectedProjectId]); // Add selectedProjectId to dependencies
const displayData = filteredData;
const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const currentItems = Array.isArray(displayData)
? displayData.slice(indexOfFirstItem, indexOfLastItem)
: [];
const paginate = (pageNumber) => setCurrentPage(pageNumber);
const totalPages = Array.isArray(displayData)
? Math.ceil(displayData.length / itemsPerPage)
: 0;
const openModal = () => {
setIsCreateModalOpen(true); setIsCreateModalOpen(true);
}; };
// const closeModal = () => {
// setIsCreateModalOpen(false);
// const modalElement = document.getElementById("managerole-modal");
// if (modalElement && !showModal) {
// modalElement.classList.remove("show");
// modalElement.style.display = "none";
// document.body.classList.remove("modal-open");
// document.querySelector(".modal-backdrop")?.remove();
// }
// setShowModal(false);
// clearCacheKey("employeeProfile");
// recallEmployeeData(showInactive, showAllEmployees ? null : selectedProjectId); // Use selectedProjectId here
// };
// const handleShow = () => setShowModal(true);
// const handleClose = () => setShowModal( false );
useEffect(() => {
if (!loading && Array.isArray(employees)) {
const sorted = [...employees].sort((a, b) => {
const nameA = `${a.firstName || ""}${a.middleName || ""}${a.lastName || ""}`.toLowerCase();
const nameB = `${b.firstName || ""}${b.middleName || ""}${b.lastName || ""}`.toLowerCase();
return nameA?.localeCompare(nameB);
});
setEmployeeList((prevList) => {
const prevJSON = JSON.stringify(prevList);
const nextJSON = JSON.stringify(sorted);
if (prevJSON !== nextJSON) {
return sorted;
}
return prevList;
});
setFilteredData((prev) => {
const prevJSON = JSON.stringify(prev);
const nextJSON = JSON.stringify(sorted);
if (prevJSON !== nextJSON) {
return sorted;
}
return prev;
});
// set currentPage to 1 only if needed
setCurrentPage((prevPage) => (prevPage !== 1 ? 1 : prevPage));
}
}, [loading, employees, selectedProjectId, showAllEmployees]);
const closeModal = () => {
setIsCreateModalOpen(false);
const modalElement = document.getElementById("managerole-modal");
if (modalElement && !showModal) {
modalElement.classList.remove("show");
modalElement.style.display = "none";
document.body.classList.remove("modal-open");
document.querySelector(".modal-backdrop")?.remove(); // Use optional chaining for safety
}
setShowModal(false);
clearCacheKey("employeeProfile");
recallEmployeeData(showInactive, showAllEmployees ? null : selectedProjectId); // Use selectedProjectId here
};
const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal(false);
const suspendEmployee = (id) => {
setemployeeLodaing(true);
EmployeeRepository.deleteEmployee(id)
.then((response) => {
showToast("Employee deleted successfully.", "success");
clearCacheKey("employeeListByProject");
clearCacheKey("allEmployeeList");
clearCacheKey("allInactiveEmployeeList");
clearCacheKey("employeeProfile");
// Recall data based on current filter states after deletion to refresh the table
recallEmployeeData(showInactive, showAllEmployees ? null : selectedProjectId); // Use selectedProjectId here
setemployeeLodaing(false);
setIsDeleteModalOpen(false);
})
.catch((error) => {
const message =
error.response?.data?.message ||
error.message ||
"An unexpected error occurred";
showToast(message, "error");
setemployeeLodaing(false);
setIsDeleteModalOpen(false);
});
};
const handleConfigData = (config) => { const handleConfigData = (config) => {
setModelConfig(config); setModelConfig(config);
}; };
useEffect(() => { // useEffect(() => {
if (modelConfig !== null) { // if (modelConfig !== null) {
openModal(); // openModal();
} // }
}, [modelConfig, isCreateModalOpen]); // }, [modelConfig, isCreateModalOpen]);
const tableRef = useRef(null); const tableRef = useRef(null);
const handleExport = (type) => { const handleExport = (type) => {
@ -215,9 +213,6 @@ const handleAllEmployeesToggle = (e) => {
setShowInactive(false); setShowInactive(false);
setShowAllEmployees(isChecked); setShowAllEmployees(isChecked);
if (!isChecked) {
setSelectedProject(selectedProjectId || "");
}
}; };
const handleEmployeeModel = (id) => { const handleEmployeeModel = (id) => {
@ -253,34 +248,18 @@ const handleAllEmployeesToggle = (e) => {
return ( return (
<> <>
{isCreateModalOpen && ( {EmpForManageRole && (
<ManageEmp employeeId={modelConfig} onClosed={closeModal} /> <GlobalModel isOpen={EmpForManageRole} closeModal={() => setEmpForManageRole( null )}>
<ManageEmp employeeId={EmpForManageRole} onClosed={()=>setEmpForManageRole(null)} />
</GlobalModel>
)} )}
{/* {showModal && (<div
className={`modal fade ${showModal ? "show" : ""} `}
tabIndex="-1"
role="dialog"
style={{ display: showModal ? "block" : "none" }}
aria-hidden={!showModal}
>
<div className="modal-dialog modal-xl modal-dialog-centered ">
<div
className="modal-content overflow-y-auto overflow-x-hidden"
style={{ maxHeight: "90vh" }}
>
<ManageEmployee
employeeId={selectedEmployeeId}
onClosed={closeModal}
/>
</div>
</div>
</div> )} */}
{showModal && ( {showModal && (
<GlobalModel isOpen={showModal} size="lg" closeModal={()=>setShowModal(false)}> <GlobalModel isOpen={showModal} size="lg" closeModal={()=>setShowModal(false)}>
<ManageEmployee <ManageEmployee
employeeId={selectedEmployeeId} employeeId={selectedEmployeeId}
onClosed={()=>setShowModal(false)} onClosed={() => setShowModal( false )}
IsAllEmployee={showAllEmployees}
/> />
</GlobalModel> </GlobalModel>
)} )}
@ -594,91 +573,90 @@ const handleAllEmployeesToggle = (e) => {
</span> </span>
</td> </td>
<td className=" d-none d-md-table-cell"> <td className=" d-none d-md-table-cell">
{moment(item.joiningDate)?.format("DD-MMM-YYYY")} {moment(item.joiningDate)?.format("DD-MMM-YYYY")}
</td>
<td>
{/* Assuming 'isActive' property exists to determine status */}
{item.isActive ? (
<span
className="badge bg-label-success"
text-capitalized=""
>
Active
</span>
) : (
<span
className="badge bg-label-danger"
text-capitalized=""
>
Inactive
</span>
)}
</td>
{Manage_Employee && (
<td className="text-end">
<div className="dropdown">
<button
className="btn btn-icon dropdown-toggle hide-arrow"
data-bs-toggle="dropdown"
>
<i className="bx bx-dots-vertical-rounded bx-md"></i>
</button>
<div className="dropdown-menu dropdown-menu-end">
<button
onClick={() =>
navigate(`/employee/${item.id}`)
}
className="dropdown-item py-1"
>
<i className="bx bx-detail bx-sm"></i> View
</button>
<button
className="dropdown-item py-1"
onClick={() => {
handleEmployeeModel(item.id);
}}
>
<i className="bx bx-edit bx-sm"></i> Edit
</button>
{!item.isSystem && (
<>
<button
className="dropdown-item py-1"
onClick={() =>
handleOpenDelete(item.id)
}
>
<i className="bx bx-task-x bx-sm"></i>{" "}
Suspend
</button>
<button
className="dropdown-item py-1"
type="button"
data-bs-toggle="modal"
data-bs-target="#managerole-modal"
onClick={() =>
handleConfigData(item.id)
}
>
<i className="bx bx-cog bx-sm"></i>{" "}
Manage Role
</button>
</>
)}
</div>
</div>
</td> </td>
)} <td>
</tr> {showInactive ? (
))} <span
className="badge bg-label-danger"
text-capitalized=""
>
Inactive
</span>
) : (
<span
className="badge bg-label-success"
text-capitalized=""
>
Active
</span>
)}
</td>
{Manage_Employee && (
<td className="text-end">
<div className="dropdown">
<button
className="btn btn-icon dropdown-toggle hide-arrow"
data-bs-toggle="dropdown"
>
<i className="bx bx-dots-vertical-rounded bx-md"></i>
</button>
<div className="dropdown-menu dropdown-menu-end">
<button
onClick={() =>
navigate(`/employee/${item.id}`)
}
className="dropdown-item py-1"
>
<i className="bx bx-detail bx-sm"></i> View
</button>
<button
className="dropdown-item py-1"
onClick={() => {
handleEmployeeModel(item.id);
}}
>
<i className="bx bx-edit bx-sm"></i> Edit
</button>
{!item.isSystem && (
<>
<button
className="dropdown-item py-1"
onClick={() =>
handleOpenDelete(item.id)
}
>
<i className="bx bx-task-x bx-sm"></i>{" "}
Suspend
</button>
<button
className="dropdown-item py-1"
type="button"
data-bs-toggle="modal"
data-bs-target="#managerole-modal"
onClick={() =>
setEmpForManageRole(item.id)
}
>
<i className="bx bx-cog bx-sm"></i>{" "}
Manage Role
</button>
</>
)}
</div>
</div>
</td>
)}
</tr>
))}
</tbody> </tbody>
</table> </table>
<div style={{ width: "1%" }}></div> <div style={{ width: "1%" }}></div>
{/* Pagination */} {/* Pagination */}
{!loading && displayData.length > itemsPerPage && ( {!loading && displayData.length > ITEMS_PER_PAGE && (
<nav aria-label="Page"> <nav aria-label="Page">
<ul className="pagination pagination-sm justify-content-end py-1"> <ul className="pagination pagination-sm justify-content-end py-1">
<li <li

View File

@ -20,6 +20,7 @@ import Avatar from "../../components/common/Avatar";
import AttendancesEmployeeRecords from "./AttendancesEmployeeRecords"; import AttendancesEmployeeRecords from "./AttendancesEmployeeRecords";
import ManageEmployee from "../../components/Employee/ManageEmployee"; import ManageEmployee from "../../components/Employee/ManageEmployee";
import { useChangePassword } from "../../components/Context/ChangePasswordContext"; import { useChangePassword } from "../../components/Context/ChangePasswordContext";
import GlobalModel from "../../components/common/GlobalModel";
const EmployeeProfile = () => { const EmployeeProfile = () => {
const { profile } = useProfile(); const { profile } = useProfile();
@ -39,11 +40,7 @@ const EmployeeProfile = () => {
setActivePill(pillKey); setActivePill(pillKey);
}; };
const closeModal = () => {
setShowModal(false);
fetchEmployeeProfile(employeeId);
};
const handleShow = () => setShowModal(true);
const fetchEmployeeProfile = async (employeeID) => { const fetchEmployeeProfile = async (employeeID) => {
try { try {
@ -104,24 +101,10 @@ const EmployeeProfile = () => {
const { openChangePassword } = useChangePassword(); const { openChangePassword } = useChangePassword();
return ( return (
<> <>
{" "}
{showModal && ( {showModal && (
<div <GlobalModel size="lg" isOpen={showModal} closeModal={()=>setShowModal(false)}>
className={`modal fade ${showModal ? "show" : ""} `} <ManageEmployee employeeId={employeeId} onClosed={()=>setShowModal(false)} />
tabIndex="-1" </GlobalModel>
role="dialog"
style={{ display: showModal ? "block" : "none" }}
aria-hidden={!showModal}
>
<div className="modal-dialog modal-xl modal-dialog-centered ">
<div
className="modal-content overflow-y-auto overflow-x-hidden"
style={{ maxHeight: "90vh" }}
>
<ManageEmployee employeeId={employeeId} onClosed={closeModal} />
</div>
</div>
</div>
)} )}
<div className="container-fluid"> <div className="container-fluid">
<Breadcrumb <Breadcrumb
@ -251,7 +234,7 @@ const EmployeeProfile = () => {
</div> </div>
<button <button
className="btn btn-primary btn-block" className="btn btn-primary btn-block"
onClick={() => handleShow()} onClick={()=>setShowModal(true)}
> >
Edit Profile Edit Profile
</button> </button>

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useMemo } from "react";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import MasterModal from "../../components/master/MasterModal"; import MasterModal from "../../components/master/MasterModal";
import { mastersList} from "../../data/masters"; import { mastersList} from "../../data/masters";
@ -9,92 +9,80 @@ import MasterTable from "./MasterTable";
import { getCachedData } from "../../slices/apiDataManager"; import { getCachedData } from "../../slices/apiDataManager";
import {useHasUserPermission} from "../../hooks/useHasUserPermission"; import {useHasUserPermission} from "../../hooks/useHasUserPermission";
import { MANAGE_MASTER } from "../../utils/constants"; import { MANAGE_MASTER } from "../../utils/constants";
import {useQueryClient} from "@tanstack/react-query";
const MasterPage = () => { const MasterPage = () => {
const [modalConfig, setModalConfig] = useState({ modalType: "", item: null, masterType: null });
const [searchTerm, setSearchTerm] = useState('');
const [filteredResults, setFilteredResults] = useState([]);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [modalConfig, setmodalConfig] = useState({modalType: "", item: null, masterType:null }); const hasMasterPermission = useHasUserPermission(MANAGE_MASTER);
const [searchTerm, setSearchTerm] = useState(''); const dispatch = useDispatch();
const [ filteredResults, setFilteredResults ] = useState( [] ); const selectedMaster = useSelector((store) => store.localVariables.selectedMaster);
const hasMasterPermission = useHasUserPermission( MANAGE_MASTER ) const queryClient = useQueryClient();
const dispatch = useDispatch();
const selectedMaster = useSelector((store)=>store.localVariables.selectedMaster)
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const openModal = () => {
setIsCreateModalOpen(true);
const { data: masterData = [], loading, error, RecallApi } = useMaster();
const openModal = () => setIsCreateModalOpen(true);
const closeModal = () => {
setIsCreateModalOpen(false);
setModalConfig(null);
// Clean up Bootstrap modal manually
const modalEl = document.getElementById('master-modal');
modalEl?.classList.remove('show');
if (modalEl) modalEl.style.display = 'none';
document.body.classList.remove('modal-open');
document.body.style.overflow = 'auto';
document.querySelectorAll('.modal-backdrop').forEach((el) => el.remove());
};
const handleModalData = (modalType, item, masterType = selectedMaster) => {
setModalConfig({ modalType, item, masterType });
};
const handleSearch = (e) => {
const value = e.target.value.toLowerCase();
setSearchTerm(value);
if (!masterData?.length) return;
const results = masterData.filter((item) =>
Object.values(item).some(
(field) => field?.toString().toLowerCase().includes(value)
)
);
setFilteredResults(results);
};
const displayData = useMemo(() => {
if (searchTerm) return filteredResults;
return queryClient.getQueryData(["masterData", selectedMaster]) || masterData;
}, [searchTerm, filteredResults, selectedMaster, masterData]);
const columns = useMemo(() => {
if (!displayData?.length) return [];
return Object.keys(displayData[0]).map((key) => ({
key,
label: key.toUpperCase(),
}));
}, [displayData]);
useEffect(() => {
if (modalConfig) openModal();
}, [modalConfig]);
useEffect(() => {
return () => {
setIsCreateModalOpen(false);
closeModal();
}; };
}, []);
const closeModal = () => {
setIsCreateModalOpen(false);
setmodalConfig(null);
const modalElement = document.getElementById('master-modal');
if (modalElement) {
modalElement.classList.remove('show');
modalElement.style.display = 'none';
document.body.classList.remove('modal-open');
const backdropElement = document.querySelector('.modal-backdrop');
if (backdropElement) {
backdropElement.classList.remove('modal-backdrop');
backdropElement.style.display = 'none';
}
}
const modalBackdropElement = document.querySelector('.modal-backdrop');
if (modalBackdropElement) {
modalBackdropElement.remove();
}
document.body.style.overflow = 'auto';
};
const {data:masterData, loading,error , RecallApi} = useMaster();
const handleSearch = (e) => {
const value = e.target.value.toLowerCase();
setSearchTerm(value);
if (!masterData.length) return;
const results = masterData.filter((item) =>
Object.values(item).some((field) =>
field && field.toString().toLowerCase().includes(value)
)
);
setFilteredResults(results);
};
const displayData = searchTerm ? filteredResults : getCachedData(selectedMaster) ? getCachedData(selectedMaster) : masterData
const columns = displayData?.length
? Object.keys(displayData[0]).map((key) => ({ key, label: key.toUpperCase() }))
: [];
const handleModalData =(modalType,item,masterType = selectedMaster)=>{
setmodalConfig({ modalType: modalType, item:item,masterType:masterType });
}
useEffect(() => {
if (modalConfig !== null) {
openModal();
}
}, [ modalConfig, isCreateModalOpen ] );
useEffect(() => {
return () => {
setIsCreateModalOpen(false)
closeModal();
};
}, [])
return ( return (
<> <>

View File

@ -36,34 +36,35 @@ const ProjectDetails = () => {
projects_Details, projects_Details,
loading: projectLoading, loading: projectLoading,
error: ProjectError, error: ProjectError,
refetch
} = useProjectDetails(projectId); } = useProjectDetails(projectId);
const dispatch = useDispatch(); const dispatch = useDispatch();
const [project, setProject] = useState(null); const [project, setProject] = useState(null);
const [projectDetails, setProjectDetails] = useState(null); // const [projectDetails, setProjectDetails] = useState(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState(""); const [error, setError] = useState("");
const fetchData = async () => { // const fetchData = async () => {
const project_cache = getCachedData("projectInfo"); // const project_cache = getCachedData("projectInfo");
if (!project_cache || project_cache?.projectId !== projectId) { // if (!project_cache || project_cache?.projectId !== projectId) {
ProjectRepository.getProjectByprojectId(projectId) // ProjectRepository.getProjectByprojectId(projectId)
.then((response) => { // .then((response) => {
setProjectDetails(response.data); // setProjectDetails(response.data);
setProject(response.data); // setProject(response.data);
cacheData("projectInfo", { projectId, data: response.data }); // cacheData("projectInfo", { projectId, data: response.data });
setLoading(false); // setLoading(false);
}) // })
.catch((error) => { // .catch((error) => {
console.error(error); // console.error(error);
setError("Failed to fetch data."); // setError("Failed to fetch data.");
setLoading(false); // setLoading(false);
}); // });
} else { // } else {
setProjectDetails(project_cache.data); // setProjectDetails(project_cache.data);
setProject(project_cache.data); // setProject(project_cache.data);
setLoading(false); // setLoading(false);
} // }
}; // };
const [activePill, setActivePill] = useState("profile"); const [activePill, setActivePill] = useState("profile");
@ -80,31 +81,14 @@ const ProjectDetails = () => {
switch (activePill) { switch (activePill) {
case "profile": { case "profile": {
return ( return (
<> <div className="row">
<div className="row"> <div className="col-xl-4 col-lg-5 col-md-5 mt-5">
<div className="col-lg-4 col-md-5 mt-5"> <AboutProject ></AboutProject>
{/* About User */}
<AboutProject data={projectDetails}></AboutProject>
<ProjectOverview project={projectId} />
{/* About User */}
</div>
<div className="col-lg-8 col-md-5 mt-5">
{/* Profile Overview */}
<ProjectProgressChart
ShowAllProject="false"
DefaultRange="1M"
/>
{/* Profile Overview */}
</div>
</div> </div>
<div className="row"> <div className="col-xl-4 col-lg-5 col-md-5 mt-5">
<div className="col-xl-4 col-lg-5 col-md-5 "> <ProjectOverview project={projectId} />
{/* Profile Overview */}
{/* <ProjectOverview project={projectId} /> */}
{/* Profile Overview */}
</div>
</div> </div>
</> </div>
); );
} }
case "teams": { case "teams": {
@ -112,7 +96,7 @@ const ProjectDetails = () => {
<div className="row"> <div className="row">
<div className="col-lg-12 col-xl-12"> <div className="col-lg-12 col-xl-12">
{/* Teams */} {/* Teams */}
<Teams project={projectDetails}></Teams> <Teams ></Teams>
{/* Teams */} {/* Teams */}
</div> </div>
</div> </div>
@ -122,7 +106,7 @@ const ProjectDetails = () => {
case "infra": { case "infra": {
return ( return (
<ProjectInfra <ProjectInfra
data={projectDetails} data={projects_Details}
onDataChange={handleDataChange} onDataChange={handleDataChange}
></ProjectInfra> ></ProjectInfra>
); );
@ -131,7 +115,7 @@ const ProjectDetails = () => {
case "workplan": { case "workplan": {
return ( return (
<WorkPlan <WorkPlan
data={projectDetails} data={projects_Details}
onDataChange={handleDataChange} onDataChange={handleDataChange}
></WorkPlan> ></WorkPlan>
); );
@ -140,7 +124,7 @@ const ProjectDetails = () => {
case "directory": { case "directory": {
return ( return (
<div className="row"> <div className="row">
<Directory IsPage={false} prefernceContacts={projectDetails.id} /> <Directory IsPage={false} prefernceContacts={projects_Details.id} />
</div> </div>
); );
} }
@ -152,29 +136,16 @@ const ProjectDetails = () => {
useEffect(() => { useEffect(() => {
dispatch(setProjectId(projectId)); dispatch(setProjectId(projectId));
setProject(projects_Details);
setProjectDetails(projects_Details);
}, [projects_Details, projectId]); }, [projects_Details, projectId]);
const handler = useCallback( const handler = useCallback(
(msg) => { (msg) => {
if (msg.keyword === "Update_Project" && project.id === msg.response.id) { if (msg.keyword === "Update_Project" && projects_Details.id === msg.response.id) {
clearCacheKey("projectInfo"); refetch()
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);
});
} }
}, },
[project, handleDataChange] [projects_Details, handleDataChange]
); );
useEffect(() => { useEffect(() => {
eventBus.on("project", handler); eventBus.on("project", handler);

View File

@ -3,10 +3,13 @@ import ProjectCard from "../../components/Project/ProjectCard";
import ManageProjectInfo from "../../components/Project/ManageProjectInfo"; import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import ProjectRepository from "../../repositories/ProjectRepository"; import ProjectRepository from "../../repositories/ProjectRepository";
import { useProjects } from "../../hooks/useProjects"; import { useProjects, useCreateProject } from "../../hooks/useProjects";
import { useDispatch } from "react-redux";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import { getCachedData, cacheData, clearCacheKey } from "../../slices/apiDataManager"; // import {
// getCachedData,
// cacheData,
// clearCacheKey,
// } from "../../slices/apiDataManager";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { useProfile } from "../../hooks/useProfile"; import { useProfile } from "../../hooks/useProfile";
import { ITEMS_PER_PAGE, MANAGE_PROJECT } from "../../utils/constants"; import { ITEMS_PER_PAGE, MANAGE_PROJECT } from "../../utils/constants";
@ -14,26 +17,33 @@ import ProjectListView from "./ProjectListView";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import { clearApiCacheKey } from "../../slices/apiCacheSlice"; import { clearApiCacheKey } from "../../slices/apiCacheSlice";
import { defaultCheckBoxAppearanceProvider } from "pdf-lib"; import { defaultCheckBoxAppearanceProvider } from "pdf-lib";
import { useMutation } from "@tanstack/react-query";
import usePagination from "../../hooks/usePagination";
import GlobalModel from "../../components/common/GlobalModel";
const ProjectList = () => { const ProjectList = () => {
const { profile: loginUser } = useProfile(); const { profile: loginUser } = useProfile();
const [listView, setListView] = useState(false); const [listView, setListView] = useState(false);
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const { projects, loading, error, refetch } = useProjects(); const { projects, loading, error, refetch } = useProjects();
const [projectList, setProjectList] = useState([]); const [projectList, setProjectList] = useState([]);
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
const [HasManageProject, setHasManageProject] = useState(
HasManageProjectPermission
);
const dispatch = useDispatch();
const [currentPage, setCurrentPage] = useState(1); const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
const [itemsPerPage] = useState(ITEMS_PER_PAGE); const [HasManageProject, setHasManageProject] = useState(HasManageProjectPermission);
const { mutate: createProject,isPending } = useCreateProject({
onSuccessCallback: () => {
setShowModal(false);
},
});
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [selectedStatuses, setSelectedStatuses] = useState([ const [selectedStatuses, setSelectedStatuses] = useState([
"b74da4c2-d07e-46f2-9919-e75e49b12731", "b74da4c2-d07e-46f2-9919-e75e49b12731",
"603e994b-a27f-4e5d-a251-f3d69b0498ba", "603e994b-a27f-4e5d-a251-f3d69b0498ba",
"ef1c356e-0fe0-42df-a5d3-8daee355492d", "ef1c356e-0fe0-42df-a5d3-8daee355492d",
"cdad86aa-8a56-4ff4-b633-9c629057dfef",
"33deaef9-9af1-4f2a-b443-681ea0d04f81", "33deaef9-9af1-4f2a-b443-681ea0d04f81",
]); ]);
@ -53,48 +63,29 @@ const ProjectList = () => {
.filter((statusId) => grouped[statusId]) .filter((statusId) => grouped[statusId])
.flatMap((statusId) => .flatMap((statusId) =>
grouped[statusId].sort((a, b) => grouped[statusId].sort((a, b) =>
a.name.toLowerCase()?.localeCompare(b.name.toLowerCase()) a.name.toLowerCase().localeCompare(b.name.toLowerCase())
) )
); );
setProjectList(sortedGrouped);
setProjectList((prev) => {
const isSame = JSON.stringify(prev) === JSON.stringify(sortedGrouped);
return isSame ? prev : sortedGrouped;
});
} }
}; };
useEffect(() => { useEffect(() => {
sortingProject(projects); if (!loading && projects) {
}, [projects, loginUser?.projects, loading]); sortingProject(projects);
}
}, [projects, loading, selectedStatuses]);
useEffect(() => { useEffect(() => {
if (loginUser) { setHasManageProject(loginUser ? HasManageProjectPermission : false);
setHasManageProject(HasManageProjectPermission);
} else {
setHasManageProject(false);
}
}, [loginUser, HasManageProjectPermission]); }, [loginUser, HasManageProjectPermission]);
const handleSubmitForm = (newProject, setloading, reset) => { const handleSubmitForm = (newProject) => {
ProjectRepository.manageProject(newProject) createProject(newProject);
.then((response) => {
const cachedProjects = getCachedData("projectslist") || [];
const updatedProjects = [...cachedProjects, response.data];
cacheData("projectslist", updatedProjects);
setProjectList((prev) => [...prev, response.data]);
setloading(false);
reset();
sortingProject(getCachedData("projectslist"));
showToast("Project Created successfully.", "success");
setShowModal(false);
})
.catch((error) => {
showToast(error.message, "error");
setShowModal(false);
});
};
const handleReFresh = () => {
if (!projects || projects.length === 0) {
refetch();
}
}; };
const handleStatusChange = (statusId) => { const handleStatusChange = (statusId) => {
@ -118,13 +109,14 @@ const ProjectList = () => {
return matchesStatus && matchesSearch; return matchesStatus && matchesSearch;
}); });
const indexOfLastItem = currentPage * itemsPerPage; const totalPages = Math.ceil(filteredProjects.length / ITEMS_PER_PAGE);
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const currentItems = filteredProjects.slice( const {
indexOfFirstItem, currentItems,
indexOfLastItem currentPage,
); paginate,
const totalPages = Math.ceil(filteredProjects.length / itemsPerPage); setCurrentPage,
} = usePagination(filteredProjects, ITEMS_PER_PAGE);
useEffect(() => { useEffect(() => {
const tooltipTriggerList = Array.from( const tooltipTriggerList = Array.from(
@ -133,67 +125,19 @@ const ProjectList = () => {
tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el)); tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el));
}, []); }, []);
const handler = useCallback(
async (msg) => {
if (HasManageProject && msg.keyword === "Create_Project") {
const updatedProjects = [...projectList, msg.response];
cacheData("projectslist", updatedProjects);
setProjectList(updatedProjects);
sortingProject(updatedProjects);
}
if (
msg.keyword === "Update_Project" &&
projectList.some((item) => item.id === msg.response.id)
) {
ProjectRepository.getProjectList()
.then((response) => {
cacheData("projectslist", response?.data);
sortingProject(response?.data);
})
.catch((e) => {
console.error(e)
});
}
},
[HasManageProject, projectList, sortingProject]
);
useEffect(() => {
eventBus.on("project", handler);
return () => eventBus.off("project", handler);
}, [handler]);
const assignProjectHandler = useCallback(
async (data) => {
clearCacheKey("projectslist");
await refetch();
sortingProject(projects);
},
[refetch]
);
useEffect(() => {
eventBus.on("assign_project_one", assignProjectHandler);
return () => eventBus.off("assign_project_one", assignProjectHandler);
}, [handler]);
return ( return (
<> <>
<div
className={`modal fade ${showModal ? "show" : ""}`} {showModal && (
tabIndex="-1" <GlobalModel isOpen={showModal} closeModal={handleClose}>
role="dialog" <ManageProjectInfo
style={{ display: showModal ? "block" : "none" }}
aria-hidden={!showModal}
>
<ManageProjectInfo
project={null} project={null}
handleSubmitForm={handleSubmitForm} handleSubmitForm={handleSubmitForm}
onClose={handleClose} onClose={handleClose}
isPending={isPending}
/> />
</div> </GlobalModel>
)}
<div className="container-fluid"> <div className="container-fluid">
<Breadcrumb <Breadcrumb
@ -262,6 +206,10 @@ const ProjectList = () => {
id: "b74da4c2-d07e-46f2-9919-e75e49b12731", id: "b74da4c2-d07e-46f2-9919-e75e49b12731",
label: "Active", label: "Active",
}, },
{
id: "cdad86aa-8a56-4ff4-b633-9c629057dfef",
label:"In Progress"
},
{ {
id: "603e994b-a27f-4e5d-a251-f3d69b0498ba", id: "603e994b-a27f-4e5d-a251-f3d69b0498ba",
label: "On Hold", label: "On Hold",
@ -318,11 +266,11 @@ const ProjectList = () => {
{listView ? ( {listView ? (
<div className="card cursor-pointer"> <div className="card cursor-pointer">
<div className="card-body p-2"> <div className="card-body p-2">
<div className="table-responsive text-nowrap py-2 "> <div className="table-responsive text-nowrap py-2 " style={{minHeight:"400px"}}>
<table className="table m-3"> <table className="table m-3">
<thead> <thead>
<tr> <tr>
<th className="text-start" colSpan={5}> <th className="text-start" colSpan={5} >
Project Name Project Name
</th> </th>
<th className="mx-2 text-start">Contact Person</th> <th className="mx-2 text-start">Contact Person</th>
@ -345,6 +293,10 @@ const ProjectList = () => {
id: "b74da4c2-d07e-46f2-9919-e75e49b12731", id: "b74da4c2-d07e-46f2-9919-e75e49b12731",
label: "Active", label: "Active",
}, },
{
id: "cdad86aa-8a56-4ff4-b633-9c629057dfef",
label:"In Progress"
},
{ {
id: "603e994b-a27f-4e5d-a251-f3d69b0498ba", id: "603e994b-a27f-4e5d-a251-f3d69b0498ba",
label: "On Hold", label: "On Hold",
@ -386,8 +338,8 @@ const ProjectList = () => {
</thead> </thead>
<tbody className="table-border-bottom-0 overflow-auto "> <tbody className="table-border-bottom-0 overflow-auto ">
{currentItems.length === 0 ? ( {currentItems.length === 0 ? (
<tr> <tr className="text-center">
<td colSpan="12" className="text-center py-4"> <td colSpan="12" rowSpan='12'style={{height:"200px"}} >
No projects found No projects found
</td> </td>
</tr> </tr>

View File

@ -1,6 +1,10 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import moment from "moment"; import moment from "moment";
import { useProjects } from "../../hooks/useProjects"; import {
useProjectDetails,
useProjects,
useUpdateProject,
} from "../../hooks/useProjects";
import { import {
getProjectStatusName, getProjectStatusName,
getProjectStatusColor, getProjectStatusColor,
@ -14,25 +18,36 @@ import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import ManageProjectInfo from "../../components/Project/ManageProjectInfo"; import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
import showToast from "../../services/toastService"; import showToast from "../../services/toastService";
import { getCachedData, cacheData } from "../../slices/apiDataManager"; import { getCachedData, cacheData } from "../../slices/apiDataManager";
import GlobalModel from "../../components/common/GlobalModel";
import {formatNumber} from "../../utils/dateUtils";
const ProjectListView = ({ projectData, recall }) => { const ProjectListView = ({ projectData, recall }) => {
const [projectInfo, setProjectInfo] = useState(projectData); const [projectInfo, setProjectInfo] = useState(projectData);
const [projectDetails, setProjectDetails] = useState(null); const { projects_Details, loading, error, refetch } = useProjectDetails(
projectInfo?.id,false
);
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const ManageProject = useHasUserPermission(MANAGE_PROJECT); const ManageProject = useHasUserPermission(MANAGE_PROJECT);
useEffect(()=>{ useEffect(() => {
setProjectInfo(projectData); setProjectInfo(projectData);
},[projectData]) }, [projectData]);
const {
mutate: updateProject,
isPending,
isSuccess,
isError,
} = useUpdateProject({
onSuccessCallback: () => {
setShowModal(false);
},
})
const handleShow = async () => { const handleShow = async () => {
try { try {
const response = await ProjectRepository.getProjectByprojectId( const { data } = await refetch();
projectInfo.id
);
setProjectDetails(response.data);
setShowModal(true); setShowModal(true);
} catch (error) { } catch (err) {
showToast("Failed to load project details", "error"); showToast("Failed to load project details", "error");
} }
}; };
@ -52,63 +67,25 @@ const ProjectListView = ({ projectData, recall }) => {
const handleFormSubmit = (updatedProject) => { const handleFormSubmit = (updatedProject) => {
if (projectInfo?.id) { if (projectInfo?.id) {
ProjectRepository.updateProject(projectInfo.id, updatedProject) updateProject({
.then((response) => { projectId: projectInfo.id,
const updatedProjectData = { updatedData: updatedProject,
...projectInfo, });
...response.data,
building: projectDetails?.building,
};
setProjectInfo(updatedProjectData);
if (getCachedData(`projectinfo-${projectInfo.id}`)) {
cacheData(`projectinfo-${projectInfo.id}`, updatedProjectData);
}
const projects_list = getCachedData("projectslist");
if (projects_list) {
const updatedProjectsList = projects_list.map((project) =>
project.id === projectInfo.id
? {
...project,
...response.data,
// tenant: project.tenant
}
: project
);
cacheData("projectslist", updatedProjectsList);
}
recall(getCachedData("projectslist"));
showToast("Project updated successfully.", "success");
setShowModal(false);
})
.catch((error) => {
showToast(error.message, "error");
});
} }
}; };
return ( return (
<> <>
{showModal && projectDetails && ( {showModal && projects_Details && (
<tr> <GlobalModel isOpen={showModal} closeModal={handleClose}> <ManageProjectInfo
<td project={projects_Details}
className="modal fade show"
tabIndex="-1"
role="dialog"
style={{ display: "block" }}
aria-hidden="false"
>
<ManageProjectInfo
project={projectDetails}
handleSubmitForm={handleFormSubmit} handleSubmitForm={handleFormSubmit}
onClose={handleClose} onClose={handleClose}
/> isPending={isPending}
</td> /></GlobalModel>
</tr>
)} )}
<tr className="py-8"> <tr className={`py-8 ${isPending ? "bg-light opacity-50 pointer-events-none" : ""} `}>
<td className="text-start" colSpan={5}> <td className="text-start" colSpan={5}>
<span <span
className="text-primary cursor-pointer" className="text-primary cursor-pointer"
@ -132,7 +109,7 @@ const ProjectListView = ({ projectData, recall }) => {
? moment(projectInfo.endDate).format("DD-MMM-YYYY") ? moment(projectInfo.endDate).format("DD-MMM-YYYY")
: "NA"} : "NA"}
</td> </td>
<td className="mx-2 text-center small">{projectInfo.plannedWork}</td> <td className="mx-2 text-center small">{formatNumber(projectInfo.plannedWork)}</td>
<td className="py-6 mx-2 text-start small align-items-center"> <td className="py-6 mx-2 text-start small align-items-center">
<ProgressBar <ProgressBar
plannedWork={projectInfo.plannedWork} plannedWork={projectInfo.plannedWork}
@ -162,14 +139,23 @@ const ProjectListView = ({ projectData, recall }) => {
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
aria-expanded="false" aria-expanded="false"
> >
<i {loading ? (
className="bx bx-dots-vertical-rounded bx-sm text-muted" <div
data-bs-toggle="tooltip" className="spinner-border spinner-border-sm text-secondary"
data-bs-offset="0,8" role="status"
data-bs-placement="top" >
data-bs-custom-class="tooltip-dark" <span className="visually-hidden">Loading...</span>
title="More Action" </div>
></i> ) : (
<i
className="bx bx-dots-vertical-rounded bx-sm text-muted"
data-bs-toggle="tooltip"
data-bs-offset="0,8"
data-bs-placement="top"
data-bs-custom-class="tooltip-dark"
title="More Action"
></i>
)}
</button> </button>
<ul className="dropdown-menu dropdown-menu-end"> <ul className="dropdown-menu dropdown-menu-end">
<li> <li>

View File

@ -41,7 +41,7 @@ export const MasterRespository = {
"Activity": ( id ) => api.delete( `/api/master/activity/delete/${ id }` ), "Activity": ( id ) => api.delete( `/api/master/activity/delete/${ id }` ),
"Application Role":(id)=>api.delete(`/api/roles/${id}`), "Application Role":(id)=>api.delete(`/api/roles/${id}`),
"Work Category": ( id ) => api.delete( `api/master/work-category/${ id }` ), "Work Category": ( id ) => api.delete( `api/master/work-category/${ id }` ),
"Contact Category": ( id ) => api.delete( `/api/master/contact-category` ), "Contact Category": ( id ) => api.delete( `/api/master/contact-category/${id}` ),
"Contact Tag" :(id)=>api.delete(`/api/master/contact-tag/${id}`), "Contact Tag" :(id)=>api.delete(`/api/master/contact-tag/${id}`),
getWorkCategory:() => api.get(`/api/master/work-categories`), getWorkCategory:() => api.get(`/api/master/work-categories`),

View File

@ -23,7 +23,11 @@ const ProjectRepository = {
deleteProject: ( id ) => api.delete( `/projects/${ id }` ), deleteProject: ( id ) => api.delete( `/projects/${ id }` ),
getProjectsByEmployee: ( id ) => api.get( `/api/project/assigned-projects/${ id }` ), getProjectsByEmployee: ( id ) => api.get( `/api/project/assigned-projects/${ id }` ),
updateProjectsByEmployee:(id,data)=>api.post(`/api/project/assign-projects/${id}`,data), 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 = { export const TasksRepository = {

View File

@ -8,7 +8,8 @@ import showToast from "./toastService";
import eventBus from "./eventBus"; import eventBus from "./eventBus";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { clearApiCacheKey } from "../slices/apiCacheSlice"; import { clearApiCacheKey } from "../slices/apiCacheSlice";
import { BASE_URL } from "../utils/constants"; import {BASE_URL} from "../utils/constants";
import { queryClient } from "../layouts/AuthLayout";
const base_Url = BASE_URL; const base_Url = BASE_URL;
let connection = null; let connection = null;
@ -57,7 +58,8 @@ export function startSignalR(loggedUser) {
data.keyword == "Create_Project" || data.keyword == "Create_Project" ||
data.keyword == "Update_Project" data.keyword == "Update_Project"
) { ) {
clearCacheKey("projectslist"); // clearCacheKey("projectslist");
queryClient.invalidateQueries(['projectslist']);
eventBus.emit("project", data); eventBus.emit("project", data);
} }
@ -77,20 +79,37 @@ export function startSignalR(loggedUser) {
} }
// if created or updated infra // if created or updated infra
if (data.keyword == "Infra") { if (data.keyword == "Infra") {
clearCacheKey("projectInfo"); queryClient.removeQueries({queryKey:["ProjectInfra"]})
eventBus.emit("infra", data); // eventBus.emit("infra", data);
}
if (data.keyword == "Task_Report") {
queryClient.removeQueries({queryKey:["Infra"]})
// eventBus.emit("infra", data);
}
if ( data.keyword == "WorkItem" )
{
queryClient.removeQueries({queryKey:["WorkItems"]})
} }
// if created or updated Employee // if created or updated Employee
if (data.keyword == "Employee") { if (data.keyword == "Employee") {
clearCacheKey("employeeListByProject"); // clearCacheKey("employeeListByProject");
clearCacheKey("allEmployeeList"); // clearCacheKey("allEmployeeList");
clearCacheKey("allInactiveEmployeeList"); // clearCacheKey("allInactiveEmployeeList");
clearCacheKey("employeeProfile"); // clearCacheKey("employeeProfile");
clearCacheKey("Attendance"); clearCacheKey("Attendance");
clearCacheKey("regularizedList") clearCacheKey("regularizedList")
clearCacheKey("AttendanceLogs") clearCacheKey("AttendanceLogs")
eventBus.emit("employee", data);
// ---we can do also----
// queryClient.removeQueries(['allEmployee', true]);
// but best practies is refetch
queryClient.invalidateQueries(['allEmployee', true]);
queryClient.invalidateQueries(['allEmployee', false]);
queryClient.invalidateQueries(['employeeProfile', data.response?.employeeId]);
queryClient.invalidateQueries(['employeeListByProject']); // optional if scope
eventBus.emit("employee", data);
} }
if (data.keyword == "Task_Report") { if (data.keyword == "Task_Report") {

View File

@ -62,6 +62,11 @@ export const checkIfCurrentDate = (dateString) => {
return currentDate?.getTime() === inputDate?.getTime(); return currentDate?.getTime() === inputDate?.getTime();
}; };
export const formatNumber = (num) => {
if (num == null || isNaN(num)) return "NA";
return Number.isInteger(num) ? num : num.toFixed(2);
};
export const formatUTCToLocalTime = (datetime) =>{ export const formatUTCToLocalTime = (datetime) =>{
return moment.utc(datetime).local().format("MMMM DD, YYYY [at] hh:mm A"); return moment.utc(datetime).local().format("MMMM DD, YYYY [at] hh:mm A");
} }

View File

@ -24,7 +24,7 @@ export const getProjectStatusColor = (statusId) => {
case "33deaef9-9af1-4f2a-b443-681ea0d04f81": case "33deaef9-9af1-4f2a-b443-681ea0d04f81":
return "bg-label-secondary"; return "bg-label-secondary";
case "cdad86aa-8a56-4ff4-b633-9c629057dfef": case "cdad86aa-8a56-4ff4-b633-9c629057dfef":
return "bg-label-success"; return "bg-label-primary";
} }
}; };