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",
"@microsoft/signalr": "^8.0.7",
"@reduxjs/toolkit": "^2.5.0",
"@tanstack/react-query": "^5.81.2",
"@tanstack/react-query-devtools": "^5.81.2",
"@types/web": "^0.0.216",
"@vitejs/plugin-react": "^4.3.4",
"apexcharts": "^4.5.0",
@ -1423,6 +1425,59 @@
"@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": {
"version": "7.20.5",
"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",
"@microsoft/signalr": "^8.0.7",
"@reduxjs/toolkit": "^2.5.0",
"@tanstack/react-query": "^5.81.2",
"@tanstack/react-query-devtools": "^5.81.2",
"@types/web": "^0.0.216",
"@vitejs/plugin-react": "^4.3.4",
"apexcharts": "^4.5.0",

View File

@ -1,21 +1,25 @@
import { DireProvider } from "./Context/DireContext";
import AppRoutes from "./router/AppRoutes";
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 = () => {
return (
<div className="app">
<DireProvider>
<AppRoutes />
</DireProvider>
<ToastContainer>
</ToastContainer>
<QueryClientProvider client={queryClient}>
<DireProvider>
<AppRoutes />
</DireProvider>
<ToastContainer />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</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 ProjectRepository from "../../repositories/ProjectRepository";
import Breadcrumb from "../../components/common/Breadcrumb";
import {useProjectDetails, useProjects} from "../../hooks/useProjects";
import {useProjectDetails, useProjectInfra, useProjects} from "../../hooks/useProjects";
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
import {MANAGE_PROJECT_INFRA} from "../../utils/constants";
import {useDispatch, useSelector} from "react-redux";
@ -21,17 +21,14 @@ const InfraPlanning = () =>
{
const {profile: LoggedUser, refetch : fetchData} = useProfile()
const dispatch = useDispatch()
const {projects,loading:project_listLoader,error:projects_error} = useProjects()
const selectedProject = useSelector((store)=>store.localVariables.projectId)
const {projectInfra, isLoading, error} = useProjectInfra( selectedProject )
const ManageInfra = useHasUserPermission( MANAGE_PROJECT_INFRA )
const {projects_Details, loading: project_deatilsLoader, error: project_error,refetch} = useProjectDetails( selectedProject )
const reloadedData = useSelector( ( store ) => store.localVariables.reload )
// useEffect( () =>
// {
// dispatch(setProjectId(projects[0]?.id))
// }, [ projects ] )
useEffect( () =>
{
@ -47,34 +44,20 @@ const InfraPlanning = () =>
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
<div className="card">
<div className="card-body" style={{ padding: "0.5rem" }}>
<div className="align-items-center">
{/* <div className="row ">
<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> */}
{ManageInfra ? (
<div className="align-items-center">
<div className="row ">
{project_deatilsLoader && ( <p>Loading...</p> )}
{( !project_deatilsLoader && projects_Details?.buildings.length === 0 ) && ( <p>No Result Found</p> )}
{(!project_deatilsLoader && projects_Details?.buildings?.length > 0) && (<InfraTable buildings={projects_Details?.buildings} projectId={projects_Details.id}/>)}
{isLoading && ( <p>Loading...</p> )}
{( !isLoading && projectInfra?.length === 0 ) && ( <p>No Result Found</p> )}
{(!isLoading && projectInfra?.length > 0) && (<InfraTable buildings={projectInfra} projectId={selectedProject}/>)}
</div>
</div>
) : (
<div 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>

View File

@ -1,21 +1,29 @@
import React, { useState,useEffect } from "react";
import React, { useState, useEffect } from "react";
import { formatDate } from "../../utils/dateUtils";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import showToast from "../../services/toastService";
import { TasksRepository } from "../../repositories/TaskRepository";
import { useReportTask } from "../../hooks/useTasks";
export const ReportTask = ({ report, closeModal, refetch }) => {
const [loading, setloading] = useState(false);
export const ReportTask = ({ report, closeModal }) => {
const { mutate: reportTask, isPending } = useReportTask({
onSuccessCallback: () => {
reset();
closeModal();
},
});
const maxPending =
report?.workItem?.plannedWork - report?.workItem?.completedWork;
const schema = z.object({
completedTask: z
.preprocess(
(val) => (val === "" || val === null || Number.isNaN(val) ? undefined : Number(val)),
completedTask: z.preprocess(
(val) =>
val === "" || val === null || Number.isNaN(val)
? undefined
: Number(val),
z
.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}`,
})
),
comment: z.string().min(1, "Comment cannot be empty"),
});
comment: z.string().min(1, "Comment cannot be empty"),
});
const {
register,
handleSubmit,
formState: {errors},
reset
formState: { errors },
reset,
} = useForm({
resolver: zodResolver(schema),
defaultValues: { completedTask: 0, comment: "" },
});
useEffect(() => {
if (report) {
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");
useEffect(() => {
if (report) {
reset({ completedTask: 0, comment: "" });
}
}, [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 = () => {
closeModal();
@ -76,153 +70,130 @@ useEffect(() => {
};
return (
<div className="container m-0">
<div className="text-center">
<p className="fs-6 fw-semibold">Report Task</p>
</div>
<div className="mb-1 row text-start">
<label
htmlFor="html5-text-input"
className="col-md-4 col-form-label"
>
Assigned Date :{" "}
</label>
<div className="col-md-8 text-start">
<label className="col-md-2 col-form-label">
{formatDate(report?.assignmentDate)}
</label>
</div>
</div>
<div className="mb-1 row text-start">
<label
htmlFor="html5-search-input"
className="col-md-4 col-form-label"
>
Assigned By :{" "}
</label>
<div className="col-md-8 text-start">
<label className=" col-form-label">{` ${report?.assignedBy.firstName} ${report?.assignedBy.lastName}`}</label>
</div>
</div>
<div className="mb-1 row text-start">
<label
htmlFor="html5-email-input"
className="col-md-4 col-form-label"
>
Wrok Area :
</label>
<div className="col-md-8 text-start text-wrap">
<label className=" col-form-label">
{" "}
{report?.workItem?.workArea?.floor?.building?.name}{" "}
<i className="bx bx-chevron-right"></i>{" "}
{report?.workItem?.workArea?.floor?.floorName}{" "}
<i className="bx bx-chevron-right"> </i>
{report?.workItem?.workArea?.areaName}
</label>
</div>
</div>
<div className="mb-1 row text-start">
<label
htmlFor="html5-email-input"
className="col-md-4 col-form-label"
>
Activity :
</label>
<div className="col-md-8 text-start text-wrap">
<label className=" col-form-label">
{report?.workItem?.activityMaster.activityName}
</label>
</div>
</div>
<div className="mb-1 row text-start">
<label
htmlFor="html5-email-input"
className="col-md-4 col-form-label"
>
Team Size :
</label>
<div className="col-md-8 text-start text-wrap">
<label className=" col-form-label">
{report?.teamMembers?.length}
</label>
</div>
</div>
<div className="mb-1 row text-start">
<label
htmlFor="html5-email-input"
className="col-md-4 col-form-label"
>
Assigned :
</label>
<div className="col-md-8 text-start text-wrap">
<label className=" col-form-label">
{report?.plannedTask} of{" "}
{report?.workItem.plannedWork -
report?.workItem.completedWork}{" "}
Pending
</label>
</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 className="mb-1 row text-start">
<label htmlFor="html5-text-input" className="col-md-4 col-form-label">
Assigned Date :{" "}
</label>
<div className="col-md-8 text-start">
<label className="col-md-2 col-form-label">
{formatDate(report?.assignmentDate)}
</label>
</div>
</div>
<div className="mb-1 row text-start">
<label htmlFor="html5-search-input" className="col-md-4 col-form-label">
Assigned By :{" "}
</label>
<div className="col-md-8 text-start">
<label className=" col-form-label">{` ${report?.assignedBy.firstName} ${report?.assignedBy.lastName}`}</label>
</div>
</div>
<div className="mb-1 row text-start">
<label htmlFor="html5-email-input" className="col-md-4 col-form-label">
Wrok Area :
</label>
<div className="col-md-8 text-start text-wrap">
<label className=" col-form-label">
{" "}
{report?.workItem?.workArea?.floor?.building?.name}{" "}
<i className="bx bx-chevron-right"></i>{" "}
{report?.workItem?.workArea?.floor?.floorName}{" "}
<i className="bx bx-chevron-right"> </i>
{report?.workItem?.workArea?.areaName}
</label>
</div>
</div>
<div className="mb-1 row text-start">
<label htmlFor="html5-email-input" className="col-md-4 col-form-label">
Activity :
</label>
<div className="col-md-8 text-start text-wrap">
<label className=" col-form-label">
{report?.workItem?.activityMaster.activityName}
</label>
</div>
</div>
<div className="mb-1 row text-start">
<label htmlFor="html5-email-input" className="col-md-4 col-form-label">
Team Size :
</label>
<div className="col-md-8 text-start text-wrap">
<label className=" col-form-label">
{report?.teamMembers?.length}
</label>
</div>
</div>
<div className="mb-1 row text-start">
<label htmlFor="html5-email-input" className="col-md-4 col-form-label">
Assigned :
</label>
<div className="col-md-8 text-start text-wrap">
<label className=" col-form-label">
{report?.plannedTask} of{" "}
{report?.workItem.plannedWork - report?.workItem.completedWork}{" "}
Pending
</label>
</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" 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 { cacheData, getCachedData } from "../../slices/apiDataManager";
import ImagePreview from "../common/ImagePreview";
import { useAuditStatus } from "../../hooks/useTasks";
import { useAuditStatus, useSubmitTaskComment } from "../../hooks/useTasks";
const ReportTaskComments = ({
commentsData,
@ -48,7 +48,18 @@ const ReportTaskComments = ({
const [loading, setloading] = useState(false);
const [comments, setComment] = useState([]);
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 {
watch,
@ -88,70 +99,16 @@ const ReportTaskComments = ({
}
}, [comments]);
const onSubmit = async (data) => {
let payload = {
...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 onSubmit = (formData) => {
submitComment({ data: formData, commentsData });
};
const selectedAuditStatus = watch("workStatus");
useEffect(() => {
reset({
approvedTask: defaultCompletedTask || 0,
});
}, [ defaultCompletedTask ] );
}, [defaultCompletedTask]);
return (
<div className="p-2 p-sm-1">
<div className="modal-body p-sm-4 p-0">
@ -233,7 +190,6 @@ const ReportTaskComments = ({
)}
</div>
</div>
<div className="d-flex align-items-center flex-wrap">
<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>
{commentsData?.approvedBy && (
<>
<hr className="my-1"/>
<div className="row">
<div className="col-12 col-sm-6">
{commentsData.approvedBy && (
<div className="fw-bold text-start d-flex align-items-center">
<i className="bx bx-user-check bx-lg me-1"></i>
<span className="me-2">Approved By:</span>
<div className="d-flex align-items-center fw-normal">
<Avatar
firstName={commentsData.approvedBy.firstName}
lastName={commentsData.approvedBy.lastName}
size="xs"
className="me-1"
/>
{commentsData.approvedBy.firstName +
" " +
commentsData.approvedBy.lastName}
</div>
<hr className="my-1" />
<div className="row">
<div className="col-12 col-sm-6">
{commentsData.approvedBy && (
<div className="fw-bold text-start d-flex align-items-center">
<i className="bx bx-user-check bx-lg me-1"></i>
<span className="me-2">Approved By:</span>
<div className="d-flex align-items-center fw-normal">
<Avatar
firstName={commentsData.approvedBy.firstName}
lastName={commentsData.approvedBy.lastName}
size="xs"
className="me-1"
/>
{commentsData.approvedBy.firstName +
" " +
commentsData.approvedBy.lastName}
</div>
</div>
)}
</div>
)}
</div>
<div className="col-12 col-sm-6">
{commentsData?.workStatus != null && (
<div className="fw-bold my-2 text-start d-flex align-items-center">
<i className="bx bx-time-five me-2"></i>
<span className="me-2">Work Status :</span>
<span className="fw-normal">
{commentsData?.workStatus.name}
{/* {commentsData?.} */}
</span>
<div className="col-12 col-sm-6">
{commentsData?.workStatus != null && (
<div className="fw-bold my-2 text-start d-flex align-items-center">
<i className="bx bx-time-five me-2"></i>
<span className="me-2">Work Status :</span>
<span className="fw-normal">
{commentsData?.workStatus.name}
{/* {commentsData?.} */}
</span>
</div>
)}
</div>
)}
</div>
</div>
<div className="col-12 d-flex">
<span className="fw-bold">Total Approved : </span><span className="ms-2">{commentsData?.completedTask }</span>
</div>
</> )}
<div className="col-12 d-flex">
<span className="fw-bold">Total Approved : </span>
<span className="ms-2">{commentsData?.completedTask}</span>
</div>
</>
)}
{commentsData?.reportedPreSignedUrls?.length > 0 && (
<>
<p className="fw-bold m-0 text-start">
@ -319,54 +275,53 @@ const ReportTaskComments = ({
)}
<form onSubmit={handleSubmit(onSubmit)} className="text-start">
{( actionAllow && !commentsData.approvedBy ) && (
{actionAllow && !commentsData.approvedBy && (
<>
<div className="row align-items-end my-1">
<div className="col-6 col-sm-4 text-start">
<label>Completed</label>
<input
className="form-control form-control-sm"
{...register("approvedTask")}
type="number"
/>
{errors.approvedTask && (
<p className="danger-text m-0">
{errors.approvedTask.message}
</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>
))
<div className="row align-items-end my-1">
<div className="col-6 col-sm-4 text-start">
<label>Completed</label>
<input
className="form-control form-control-sm"
{...register("approvedTask")}
type="number"
/>
{errors.approvedTask && (
<p className="danger-text m-0">
{errors.approvedTask.message}
</p>
)}
</select>
{errors.workStatus && (
<div className="danger-text">
{errors.workStatus.message}
</div>
)}
</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>
{errors.workStatus && (
<div className="danger-text">
{errors.workStatus.message}
</div>
)}
</div>
</div>
</div>
</>
)}
<i className="bx bx-comment-detail me-2"></i>
@ -382,10 +337,16 @@ const ReportTaskComments = ({
)}
<div
className={` ${
(actionAllow && !commentsData.approvedBy)? " d-flex justify-content-between" : "text-end"
actionAllow && !commentsData.approvedBy
? " d-flex justify-content-between"
: "text-end"
} mt-2`}
>
<div className={`form-check ${!(actionAllow && !commentsData.approvedBy) && "d-none"} `}>
<div
className={`form-check ${
!(actionAllow && !commentsData.approvedBy) && "d-none"
} `}
>
<input
className="form-check-input"
type="checkbox"
@ -403,15 +364,16 @@ const ReportTaskComments = ({
className="btn btn-sm btn-secondary"
onClick={closeModal}
data-bs-dismiss="modal"
disabled={isPending}
>
Close
</button>
<button
type="submit"
className="btn btn-sm btn-primary ms-2"
disabled={loading}
disabled={isPending}
>
{loading
{isPending
? actionAllow
? "Please Wait..."
: "Send..."

View File

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

View File

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

View File

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

View File

@ -1,12 +1,13 @@
import React, { useEffect, useState } from "react";
import useMaster from "../../hooks/masterHook/useMaster";
import React, { useEffect, useRef, useState, useMemo } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { RolesRepository } from "../../repositories/MastersRepository";
import { useEmployeeRoles } from "../../hooks/useEmployees";
import { useDispatch } from "react-redux";
import { useEmployeeRoles, useUpdateEmployeeRoles } from "../../hooks/useEmployees";
import useMaster from "../../hooks/masterHook/useMaster";
import { changeMaster } from "../../slices/localVariablesSlice";
import { RolesRepository } from "../../repositories/MastersRepository";
import showToast from "../../services/toastService";
const formSchema = z.object({
@ -15,44 +16,17 @@ const formSchema = z.object({
const ManageRole = ( {employeeId, onClosed} ) =>
{
const disptach = useDispatch();
useEffect(()=>{
disptach(changeMaster("Application Role"));
},[disptach])
const [isLoading, setIsLoading] = useState(false);
const { employeeRoles, loading } = useEmployeeRoles(employeeId);
const { data, loading: roleLoading } = useMaster();
const dispatch = useDispatch();
const formStateRef = useRef({});
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(() => {
if (employeeRoles && data) {
if (employeeRoles.length > 0) {
const updatedRoles = buildDefaultRoles();
setInitialRoles(updatedRoles);
} else {
setInitialRoles({});
}
} else {
setInitialRoles({});
}
}, [employeeRoles, data]);
dispatch(changeMaster("Application Role"));
}, [dispatch]);
const { employeeRoles = [], loading: empLoading } = useEmployeeRoles(employeeId);
const { data: roles = [], loading: roleLoading } = useMaster();
const {
register,
handleSubmit,
@ -60,167 +34,123 @@ useEffect(()=>{
reset,
} = useForm({
resolver: zodResolver(formSchema),
defaultValues: { selectedRole: {} },
});
useEffect(() => {
if (!data) return;
const updatedRoles = {};
data.forEach((role) => {
const isRoleEnabled = employeeRoles?.some(
const {
updateRoles,
isPending : isLoading,
isError,
error,
} = useUpdateEmployeeRoles({
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
);
updatedRoles[role.id] = isRoleEnabled || false;
defaults[role.id] = enabled;
});
setInitialRoles(updatedRoles);
reset({
selectedRole: updatedRoles,
});
}, [employeeRoles, data, reset]);
// 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) {
// result.push({
// id: existingRole?.id || "00000000-0000-0000-0000-000000000000",
// employeeId,
// 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
return defaults;
}, [roles, employeeRoles]);
// Avoid infinite loop by comparing previous form values
useEffect(() => {
const prev = JSON.stringify(formStateRef.current);
const next = JSON.stringify(selectedRoleDefaults);
if (prev !== next) {
formStateRef.current = selectedRoleDefaults;
reset({ selectedRole: selectedRoleDefaults });
}
}, [selectedRoleDefaults, reset]);
const onSubmit = async (formData) => {
// setIsLoading(true);
const updates = [];
const selected = formData.selectedRole;
for (const [roleId, isChecked] of Object.entries(selected)) {
const existing = employeeRoles.find((r) => r.roleId === roleId);
const wasChecked = !!existing?.isEnabled;
if (isChecked !== wasChecked) {
result.push({
id: existingRole?.id || "00000000-0000-0000-0000-000000000000",
updates.push({
id: existing?.id || "00000000-0000-0000-0000-000000000000",
employeeId,
roleId,
isEnabled: isChecked,
});
}
}
if (result.length === 0) {
if (updates.length === 0) {
showToast("No changes made", "info");
setIsLoading(false);
// setIsLoading(false);
return;
}
console.log(result);
RolesRepository.createEmployeeRoles(result)
.then(() => {
showToast("Roles updated successfully", "success");
setIsLoading(false);
reset();
onClosed();
})
.catch((error) => {
const message = error?.response?.data?.message || error?.message || "Error occured during api calling"
showToast(message, "error");
setIsLoading(false);
});
// try {
// await RolesRepository.createEmployeeRoles(updates);
// showToast("Roles updated successfully", "success");
// reset();
// onClosed();
// } catch (err) {
// const message =
// err?.response?.data?.message || err?.message || "Error occurred while updating roles";
// showToast(message, "error");
// } finally {
// setIsLoading(false);
// }
updateRoles(updates);
};
const isLoadingData = roleLoading || empLoading;
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)}>
<div className="text-start my-0">
<div className="text-start mb-3">
<p className="lead">Select Roles</p>
</div>
<div
className="d-flex flex-wrap justify-content-between align-items-center pb-5 "
style={{ maxHeight: "70vh", overflowY: "scroll" }}
>
{roleLoading && <p>Loading...</p>}
{loading && <p>Loading...</p>}
{data &&
data.map((item) => (
<div className="col-md-6 col-lg-4 mb-4" key={item.id}>
{isLoadingData ? (
<p>Loading...</p>
) : (
<div
className="d-flex flex-wrap justify-content-between pb-4"
style={{ maxHeight: "70vh", overflowY: "auto" }}
>
{roles.map((role) => (
<div className="col-md-6 col-lg-4 mb-3" key={role.id}>
<div className="form-check ms-2 text-start">
<input
className="form-check-input"
type="checkbox"
id={item.id}
{...register(`selectedRole.${item.id}`)}
id={role.id}
{...register(`selectedRole.${role.id}`)}
/>
<label
className="form-check-label text-bold"
htmlFor={item.id}
>
<small>{item.role || "--"}</small>
<label className="form-check-label" htmlFor={role.id}>
<small>{role.role || "--"}</small>
</label>
</div>
</div>
))}
</div>
{errors.selected && (
<div className="text-center">{errors.selected.message}</div>
</div>
)}
<div className="col-12 text-center">
<button type="submit" className="btn btn-sm btn-primary me-3">
{isLoading ? "Please Wait" : "Submit"}
{errors.selectedRole && (
<div className="text-danger text-center">
{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
type="reset"
@ -232,11 +162,7 @@ useEffect(()=>{
</button>
</div>
</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";
const Header = () => {
const { profile } = useProfile();
const {profile} = useProfile();
const location = useLocation();
const dispatch = useDispatch(changeMaster("Job Role"));
const dispatch = useDispatch();
const { data, loading } = useMaster();
const navigate = useNavigate();
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
@ -121,10 +121,10 @@ const Header = () => {
[fetchData,projectNames,selectedProject]
);
useEffect(() => {
eventBus.on("assign_project_one", handler);
return () => eventBus.off("assign_project_one", handler);
}, [handler]);
// useEffect(() => {
// eventBus.on("assign_project_one", handler);
// return () => eventBus.off("assign_project_one", handler);
// }, [handler]);
const newProjectHandler = useCallback(
async (msg) => {
@ -140,10 +140,26 @@ const Header = () => {
[HasManageProjectPermission,projectNames]
);
// useEffect(() => {
// eventBus.on("project", newProjectHandler);
// return () => eventBus.off("project", newProjectHandler);
// }, [handler]);
useDispatch( () =>
{
dispatch(changeMaster("Job Role"))
},[])
useEffect(() => {
eventBus.on("project", newProjectHandler);
return () => eventBus.off("project", newProjectHandler);
}, [handler]);
eventBus.on("assign_project_one", handler);
eventBus.on("project", newProjectHandler);
return () => {
eventBus.off("assign_project_one", handler);
eventBus.off("project", newProjectHandler);
};
}, [handler, newProjectHandler]);
return (
<nav
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
className="rounded-circle bx bx-building-house bx-sm-lg bx-md"
></i>
<div className="btn-group">
<button
@ -215,6 +230,7 @@ const Header = () => {
)}
</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">
<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 moment from "moment";
import { getProjectStatusName } from "../../utils/projectStatus";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { MANAGE_PROJECT } from "../../utils/constants";
import { cacheData, getCachedData } from "../../slices/apiDataManager";
import {useProjectDetails, useUpdateProject} from "../../hooks/useProjects";
import {useParams} from "react-router-dom";
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
import {MANAGE_PROJECT} from "../../utils/constants";
import GlobalModel from "../common/GlobalModel";
import ManageProjectInfo from "./ManageProjectInfo";
import ProjectRepository from "../../repositories/ProjectRepository";
import showToast from "../../services/toastService";
const AboutProject = ({ data }) => {
const [CurrentProject, setCurrentProject] = useState(data);
const [showModal, setShowModal] = useState(false);
useEffect(() => {
setCurrentProject(data);
}, [data]);
const manageProject = useHasUserPermission(MANAGE_PROJECT);
const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal(false);
const handleFormSubmit = (updatedProject, setLoading) => {
if ( CurrentProject?.id )
import {useQueryClient} from "@tanstack/react-query";
const AboutProject = () =>
{
const [ IsOpenModal, setIsOpenModal ] = useState( false )
const {mutate: UpdateProjectDetails, isPending} = useUpdateProject( {
onSuccessCallback: () =>
{
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");
});
setIsOpenModal(false)
}
};
} )
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 (
<>
<div
className={`modal fade ${showModal ? "show" : ""}`}
tabIndex="-1"
role="dialog"
style={{ display: showModal ? "block" : "none" }}
aria-hidden={!showModal}
>
<ManageProjectInfo
project={CurrentProject}
handleSubmitForm={handleFormSubmit}
onClose={handleClose}
></ManageProjectInfo>
</div>
{data && (
{IsOpenModal && (
<GlobalModel isOpen={IsOpenModal} closeModal={()=>setIsOpenModal(false)}>
<ManageProjectInfo
project={projects_Details}
handleSubmitForm={handleFormSubmit}
onClose={() => setIsOpenModal( false )}
isPending={isPending}
/>
</GlobalModel>
)}
{projects_Details && (
<>
<div className="card mb-6">
<div className="card-header text-start">
<h6 className="card-action-title mb-0">
@ -87,19 +57,19 @@ const AboutProject = ({ data }) => {
<li className="d-flex align-items-center mb-3">
<i className="bx bx-cog"></i>
<span className="fw-medium mx-2">Name:</span>{" "}
<span>{data.name}</span>
<span>{projects_Details.name}</span>
</li>
<li className="d-flex align-items-center mb-3">
<i className="bx bx-fingerprint"></i>
<span className="fw-medium mx-2">Nick Name:</span>{" "}
<span> {data.shortName} </span>
<span> {projects_Details.shortName} </span>
</li>
<li className="d-flex align-items-center mb-3">
<i className="bx bx-check"></i>
<span className="fw-medium mx-2">Start Date:</span>{" "}
<span>
{data.startDate
? moment(data.startDate).format("DD-MMM-YYYY")
{projects_Details.startDate
? moment(projects_Details.startDate).format("DD-MMM-YYYY")
: "N/A"}
</span>
</li>
@ -107,31 +77,31 @@ const AboutProject = ({ data }) => {
<i className="bx bx-stop-circle"></i>{" "}
<span className="fw-medium mx-2">End Date:</span>{" "}
<span>
{data.endDate
? moment(data.endDate).format("DD-MMM-YYYY")
{projects_Details.endDate
? moment(projects_Details.endDate).format("DD-MMM-YYYY")
: "N/A"}
</span>
</li>
<li className="d-flex align-items-center mb-3">
<i className="bx bx-trophy"></i>
<span className="fw-medium mx-2">Status:</span>{" "}
<span>{getProjectStatusName(data.projectStatusId)}</span>
<span>{projects_Details?.projectStatus?.status}</span>
</li>
<li className="d-flex align-items-center mb-3">
<i className="bx bx-user"></i>
<span className="fw-medium mx-2">Contact:</span>{" "}
<span>{data.contactPerson}</span>
<span>{projects_Details.contactPerson}</span>
</li>
<li className="d-flex flex-column align-items-start mb-3">
<div className="d-flex align-items-center">
<i className="bx bx-flag"></i>
<span className="fw-medium mx-2">Address:</span>
{data.projectAddress?.length <= 20 && (
<span>{data.projectAddress}</span>
{projects_Details.projectAddress?.length <= 20 && (
<span>{projects_Details.projectAddress}</span>
)}
</div>
{data.projectAddress?.length > 20 && (
<div className="ms-4 text-start">{data.projectAddress}</div>
{projects_Details.projectAddress?.length > 20 && (
<div className="ms-4 text-start">{projects_Details.projectAddress}</div>
)}
</li>
@ -144,7 +114,7 @@ const AboutProject = ({ data }) => {
}`}
data-bs-toggle="modal"
data-bs-target="#edit-project-modal"
onClick={handleShow}
onClick={()=>setIsOpenModal(true)}
>
Modify Details
</button>
@ -152,10 +122,12 @@ const AboutProject = ({ data }) => {
</li>
</ul>
</div>
</div>
)}
</div>
</>
)}
{!data && <span>loading...</span>}
{isLoading && <span>loading...</span>}
</>
);
};

View File

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

View File

@ -11,13 +11,12 @@ import { TasksRepository } from "../../repositories/ProjectRepository";
import showToast from "../../services/toastService";
import { useProjectDetails } from "../../hooks/useProjects";
import eventBus from "../../services/eventBus";
import { useCreateTask } from "../../hooks/useTasks";
const AssignTask = ({ assignData, onClose, setAssigned }) => {
const maxPlanned =
assignData?.workItem?.workItem?.plannedWork -
assignData?.workItem?.workItem?.completedWork;
// Zod schema for form validation
assignData?.workItem?.plannedWork -
assignData?.workItem?.completedWork;
const schema = z.object({
selectedEmployees: z
.array(z.string())
@ -41,15 +40,16 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
const [isHelpVisibleTarget, setIsHelpVisibleTarget] = useState(false);
const helpPopupRefTarget = useRef(null);
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 infoRef1 = useRef(null);
// Initialize Bootstrap Popovers on component mount
useEffect(() => {
// Check if Bootstrap is available globally
if (typeof bootstrap !== "undefined") {
if (infoRef.current) {
new bootstrap.Popover(infoRef.current, {
@ -71,8 +71,7 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
} else {
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(
(store) => store.localVariables.projectId
);
@ -82,14 +81,11 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
recallEmployeeData,
} = useEmployeesAllOrByProjectId(selectedProject, false);
const dispatch = useDispatch();
const { loading } = useMaster(); // Assuming this is for jobRoleData loading
const jobRoleData = getCachedData("Job Role");
const { loading } = useMaster();
const {data:jobRoleData} = useMaster();
// Local component states
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
// React Hook Form setup
const [displayedSelection, setDisplayedSelection] = useState("");
const {
handleSubmit,
control,
@ -97,50 +93,43 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
watch,
formState: { errors },
reset,
trigger, // <--- IMPORTANT: Destructure 'trigger' here
trigger,
} = useForm({
defaultValues: {
selectedEmployees: [],
description: "",
plannedTask: "",
},
resolver: zodResolver(schema), // Integrate Zod schema with react-hook-form
resolver: zodResolver(schema),
});
// Handler for employee checkbox changes
const handleCheckboxChange = (event, user) => {
const isChecked = event.target.checked;
let updatedSelectedEmployees = watch("selectedEmployees") || []; // Get current selected employees from form state
let updatedSelectedEmployees = watch("selectedEmployees") || [];
if (isChecked) {
// Add employee if checked and not already in the list
if (!updatedSelectedEmployees.includes(user.id)) {
updatedSelectedEmployees = [...updatedSelectedEmployees, user.id];
}
} else {
// Remove employee if unchecked
updatedSelectedEmployees = updatedSelectedEmployees.filter(
updatedSelectedEmployees = updatedSelectedEmployees?.filter(
(id) => id !== user.id
);
}
// Update the form state with the new list of selected employees
setValue("selectedEmployees", updatedSelectedEmployees);
trigger("selectedEmployees"); // <--- IMPORTANT: Trigger validation here
trigger("selectedEmployees");
};
// Effect to dispatch action for Job Role master data
useEffect(() => {
dispatch(changeMaster("Job Role"));
// Cleanup function to reset selected role when component unmounts or dispatch changes
return () => setSelectedRole("all");
}, [dispatch]);
// Handler for role filter change
const handleRoleChange = (event) => {
setSelectedRole(event.target.value);
};
// Filter employees based on selected role
const filteredEmployees =
selectedRole === "all"
? employees
@ -148,38 +137,21 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
(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 = {
taskTeam: taskTeamWithDetails,
plannedTask: data.plannedTask,
description: data.description,
assignmentDate: new Date().toISOString(), // Current date/time
workItemId: assignData?.workItem?.workItem.id,
assignmentDate: new Date().toISOString(),
workItemId: assignData?.workItem.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");
}
assignTask({payload:formattedData,workAreaId:assignData?.workArea?.id});
};
// Handler to close the modal and reset form
const closedModel = () => {
reset();
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="me-2 m-0 font-bold">Work Location :</span>
{[
assignData?.building?.name,
assignData?.building?.buildingName,
assignData?.floor?.floorName,
assignData?.workArea?.areaName,
assignData?.workItem?.workItem?.activityMaster?.activityName,
assignData?.workItem?.activityMaster?.activityName,
]
.filter(Boolean) // Filter out any undefined/null values
.map((item, index, array) => (
@ -359,7 +331,7 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
"selectedEmployees",
updatedSelected
);
trigger("selectedEmployees"); // <--- IMPORTANT: Trigger validation on removing badge
trigger("selectedEmployees");
}}
>
<i className="icon-base bx bx-x icon-md "></i>
@ -376,11 +348,10 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
{!loading && errors.selectedEmployees && (
<div className="danger-text mt-1">
<p>{errors.selectedEmployees.message}</p>{" "}
{/* Use message from Zod schema */}
</div>
)}
{/* Pending Task of Activity section */}
<div className="col-md text-start mx-0 px-0">
<div className="form-check form-check-inline mt-3 px-1">
<label
@ -393,12 +364,12 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
htmlFor="inlineCheckbox1"
>
<strong>
{assignData?.workItem?.workItem?.plannedWork -
assignData?.workItem?.workItem?.completedWork}
{assignData?.workItem?.plannedWork -
assignData?.workItem?.completedWork}
</strong>{" "}
<u>
{
assignData?.workItem?.workItem?.activityMaster
assignData?.workItem?.activityMaster
?.unitOfMeasurement
}
</u>

View File

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

View File

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

View File

@ -1,17 +1,15 @@
import React, { useState, useEffect } from "react";
import React, { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { useActivitiesMaster, useWorkCategoriesMaster } from "../../../hooks/masterHook/useMaster";
import { useProjectDetails } from "../../../hooks/useProjects";
import { useDispatch, useSelector } from "react-redux";
import ProjectRepository from "../../../repositories/ProjectRepository";
import {
cacheData,
clearCacheKey,
getCachedData,
} from "../../../slices/apiDataManager";
useActivitiesMaster,
useWorkCategoriesMaster,
} from "../../../hooks/masterHook/useMaster";
import { useManageTask } from "../../../hooks/useProjects";
import { cacheData, getCachedData } from "../../../slices/apiDataManager";
import { refreshData } from "../../../slices/localVariablesSlice";
import showToast from "../../../services/toastService";
@ -20,18 +18,13 @@ const taskSchema = z
activityID: z.string().min(1, "Activity is required"),
workCategoryId: z.string().min(1, "Work Category is required"),
plannedWork: z.number().min(1, "Planned Work must be greater than 0"),
completedWork: z.number().min( 0, "Completed Work must be greater than 0" ),
comment:z.string()
completedWork: z.number().min(0, "Completed Work must be ≥ 0"),
comment: z.string(),
})
.refine(
(data) =>
data.completedWork === undefined ||
data.completedWork <= data.plannedWork,
{
message: "Completed Work cannot be greater than Planned Work",
path: ["completedWork"], // error will show next to this field
}
);
.refine((data) => data.completedWork <= data.plannedWork, {
message: "Completed Work cannot be greater than Planned Work",
path: ["completedWork"],
});
const EditActivityModal = ({
workItem,
@ -39,29 +32,12 @@ const EditActivityModal = ({
building,
floor,
onClose,
}) => {
const selectedProject = useSelector(
(store) => store.localVariables.projectId
);
const defaultModel = {
activityID: 0,
workCategoryId: 0,
plannedWork: 0,
completedWork: 0,
comment:""
};
const { projects_Details, refetch } = useProjectDetails(selectedProject);
const [ActivityUnit, setActivityUnit] = useState("");
const { activities, loading, error } = useActivitiesMaster();
const { categories, categoryLoading, categoryError } =
useWorkCategoriesMaster();
const [formData, setFormData] = useState(defaultModel);
} ) =>
{
const { activities, loading: loadingActivities } = useActivitiesMaster();
const { categories, loading: loadingCategories } = useWorkCategoriesMaster();
const [selectedActivity, setSelectedActivity] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [activityData, setActivityData] = useState([]);
const [categoryData, setCategoryData] = useState([]);
const dispatch = useDispatch();
const {
register,
@ -73,359 +49,214 @@ const EditActivityModal = ({
watch,
} = useForm({
resolver: zodResolver(taskSchema),
defaultValues: defaultModel,
defaultValues: {
activityID: "",
workCategoryId: "",
plannedWork: 0,
completedWork: 0,
comment: "",
},
});
const handleActivityChange = (e) => {
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 ) =>
const { mutate: UpdateTask, isPending } = useManageTask({
onSuccessCallback: (response) =>
{
setIsSubmitting(true)
const updatedProject = { ...projects_Details };
const finalData = {
showToast( response?.message, "success" )
onClose()
}
} );
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,
id: workItem?.workItem?.id ?? workItem?.id,
buildingID: building?.id,
floorId: floor?.id,
workAreaId: workArea?.id,
};
ProjectRepository.manageProjectTasks([finalData])
.then((response) => {
if (response?.data[0]) {
const { workItemId, workItem } = response.data[0];
let finalUpdatedWorkItem = null;
const newProject = {
...updatedProject,
buildings: updatedProject.buildings.map((building) =>
building.id === finalData.buildingID
? {
...building,
floors: building.floors.map((floor) =>
floor.id === finalData.floorId
? {
...floor,
workAreas: floor.workAreas.map((workArea) =>
workArea.id === workItem?.workAreaId
? {
...workArea,
workItems: (() => {
const exists = workArea.workItems.some(
(item) =>
String(
item?.workItem?.id ?? item?.id
) === String(finalData.id)
);
finalUpdatedWorkItem = workItem;
return exists
? workArea.workItems.map((item) =>
String(
item?.workItem?.id ?? item?.id
) === String(finalData.id)
? workItem
: item
)
: [...workArea.workItems, workItem];
})(),
}
: 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]);
UpdateTask([payload])
}
return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
<div className="modal-content">
<div className="modal-body">
<div className="row">
<button
type="button"
className="btn-close"
aria-label="Close"
onClick={onClose}
/>
<div className="text-center mb-1">
<h5 className="mb-1">Manage 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>
<form className="row g-2 p-2 p-md-1" onSubmit={handleSubmit(onSubmitForm)}>
<div className="text-center mb-1">
<h5 className="mb-1">Manage Task</h5>
</div>
{/* Select Floor */}
<div className="col-6 col-md-6">
<label className="form-label" htmlFor="floorId">
Select Floor
</label>
<div className="row g-2">
<div className="col-12 col-md-6">
<label className="form-label">Select Building</label>
<input
className="form-control form-control-sm"
value={building?.buildingName}
disabled
/>
</div>
<input
type="text"
className="form-control form-control-sm"
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 className="col-12 col-md-6">
<label className="form-label">Select Floor</label>
<input
className="form-control form-control-sm"
value={floor?.floorName}
disabled
/>
</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>
<td colSpan="4" className="text-start table-cell">
<div className="row ps-2">
{/* <div className="col-1 col-md-1 d-flex justify-content-between align-items-center " >
<button
className="btn me-2"
>
</button>
</div> */}
<div className="col-12 ps-8">
<div className="row">
<div className="d-flex col-5">

View File

@ -1,236 +1,189 @@
import React, { useState, useEffect } from "react";
import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import showToast from "../../../services/toastService";
import { useManageProjectInfra } from "../../../hooks/useProjects";
import { useSelector } from "react-redux";
// Schema
const floorSchema = z.object({
buildingId: z
.string()
.refine((val) => val !== "0", {
message: "Building is required",
}),
.refine((val) => val !== "0", { message: "Building is required" }),
id: z.string().optional(),
floorName: z.string().min(1, "Floor Name is required"),
});
const defaultModel = {
const defaultValues = {
id: "0",
floorName: "",
buildingId: "0",
};
const FloorModel = ({
project,
onClose,
onSubmit,
clearTrigger,
onClearComplete,
}) => {
const [formData, setFormData] = useState(defaultModel);
const [selectedBuilding, setSelectedBuilding] = useState({});
const [buildings, setBuildings] = useState(project?.buildings || []);
const FloorModel = ({ project, onClose, onSubmit }) => {
const selectedProject = useSelector(
(store) => store.localVariables.projectId
);
const [selectedBuilding, setSelectedBuilding] = useState(null);
const {
register,
handleSubmit,
setValue,
reset,
watch,
formState: { errors },
} = useForm({
defaultValues,
resolver: zodResolver(floorSchema),
defaultValues: defaultModel,
});
const watchId = watch("id");
const watchBuildingId = watch("buildingId");
const { mutate: ManageFloor, isPending } = useManageProjectInfra({
onSuccessCallback: (data, variables) => {
showToast(
watchId != "0"
? "Floor updated Successfully"
: "Floor created Successfully",
"success"
);
reset({ id: "0", floorName: ""});
// onClose?.();
},
});
useEffect(() => {
if (clearTrigger) {
reset(defaultModel);
onClearComplete();
}
}, [clearTrigger, onClearComplete, reset]);
reset(defaultValues);
}, []);
useEffect(() => {
const building = project?.find((b) => b.id === watchBuildingId);
setSelectedBuilding(building || null);
}, [watchBuildingId, project]);
const handleBuildigChange = (e) => {
const buildingId = e.target.value;
const building = buildings.find((b) => b.id === String(buildingId));
if (building) {
setSelectedBuilding(building);
setFormData({
id: "",
floorName: "",
buildingId: building.id,
});
setValue("buildingId", building.id, { shouldValidate: true }); // trigger validation
setValue("id", "0");
} else {
setSelectedBuilding({});
setFormData({
id: "",
floorName: "",
buildingId: "0",
});
setValue("buildingId", "0", { shouldValidate: true }); // trigger validation
}
const handleBuildingChange = (e) => {
const id = e.target.value;
setValue("buildingId", id, { shouldValidate: true });
setValue("id", "0");
setValue("floorName", "");
};
const handleFloorChange = (e) => {
const id = e.target.value;
const floor = selectedBuilding.floors?.find((b) => b.id === String(id));
setValue("id", id);
const floor = selectedBuilding?.floors?.find((f) => f.id === id);
if (floor) {
setFormData({
id: floor.id,
floorName: floor.floorName,
buildingId: selectedBuilding.id,
});
setValue("floorName", floor.floorName);
} else {
setFormData({
id: "0",
floorName: "",
buildingId: selectedBuilding.id,
});
setValue("floorName", "");
}
};
const onFormSubmit = (data) => {
if (data.id === "0") {
data.id = null;
}
const isEdit = data.id !== "0";
const payload = {
...data,
id: isEdit ? data.id : null,
};
let infraObject = [
{
building: null,
floor: payload,
workArea: null,
},
];
onSubmit(data);
reset({ floorName: "" });
if (data.id !== null) {
showToast("Floor updated successfully.", "success");
} else {
showToast("Floor created successfully.", "success");
}
ManageFloor({ infraObject, projectId: selectedProject });
};
return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
<div className="modal-content">
<div className="modal-body">
<div className="row">
<button
type="button"
className="btn-close"
aria-label="Close"
onClick={onClose}
/>
<div className="text-center mb-1">
<h5 className="mb-1">Manage Floors - {project.name}</h5>
</div>
<form className="row g-2" onSubmit={handleSubmit(onFormSubmit)}>
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="buildingId">
Select Building
</label>
<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>
<form className="row g-2" onSubmit={handleSubmit(onFormSubmit)}>
<div className="text-center mb-1">
<h5 className="mb-1">Manage Floor</h5>
</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,
} from "../../../slices/apiDataManager";
const InfraTable = ({ buildings, projectId, signalRHandler }) => {
const InfraTable = ({ buildings, projectId}) => {
const [projectBuilding, setProjectBuilding] = useState([]);
const [expandedBuildings, setExpandedBuildings] = useState([]);
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
this building.
</p>
{/* <button
type="button"
className="btn btn-xs btn-primary"
onClick={() => handleAddFloor(building)}
>
<i className="bx bx-plus-circle me-2"></i>
Add Floors
</button> */}
</div>
</td>
</tr>
@ -120,34 +113,34 @@ const InfraTable = ({ buildings, projectId, signalRHandler }) => {
}
}, [buildings]);
const handler = useCallback(
(msg) => {
if (msg.projectIds.some((item) => item == projectId)) {
try {
ProjectRepository.getProjectByprojectId(projectId)
.then((response) => {
cacheData("projectInfo", {
projectId: projectId,
data: response.data,
});
setProjectBuilding(response?.data?.buildings);
signalRHandler?.(response?.data);
showToast(msg.message, "info");
})
.catch((error) => {
console.error(error);
});
} catch (e) {
console.error(e);
}
}
},
[buildings]
);
useEffect(() => {
eventBus.on("infra", handler);
return () => eventBus.off("infra", handler);
}, [handler]);
// const handler = useCallback(
// (msg) => {
// if (msg.projectIds.some((item) => item == projectId)) {
// try {
// ProjectRepository.getProjectByprojectId(projectId)
// .then((response) => {
// cacheData("projectInfo", {
// projectId: projectId,
// data: response.data,
// });
// setProjectBuilding(response?.data?.buildings);
// signalRHandler?.(response?.data);
// showToast(msg.message, "info");
// })
// .catch((error) => {
// console.error(error);
// });
// } catch (e) {
// console.error(e);
// }
// }
// },
// [buildings]
// );
// useEffect(() => {
// eventBus.on("infra", handler);
// return () => eventBus.off("infra", handler);
// }, [handler]);
return (
<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 { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import {
useActivitiesMaster,
useWorkCategoriesMaster,
} from "../../../hooks/masterHook/useMaster";
import { useManageTask } from "../../../hooks/useProjects";
import showToast from "../../../services/toastService";
const taskSchema = z.object({
buildingID: z.string().min(1, "Building is required"),
@ -14,433 +16,267 @@ const taskSchema = z.object({
activityID: z.string().min(1, "Activity is required"),
workCategoryId: z.string().min(1, "Work Category is required"),
plannedWork: z.number().min(1, "Planned Work must be greater than 0"),
completedWork: z.number().min( 0, "Completed Work must be greater than 0" ),
comment:z.string(),
completedWork: z.number().min(0, "Completed Work must be 0 or more"),
comment: z.string(),
});
const defaultModel = {
id: null,
buildingID: "", // Changed from "0"
floorId: "", // Changed from "0"
workAreaId: "", // Changed from "0"
activityID: "", // Changed from null
workCategoryId: "", // Kept as empty
buildingID: "",
floorId: "",
workAreaId: "",
activityID: "",
workCategoryId: "",
plannedWork: 0,
completedWork: 0,
comment: ""
comment: "",
};
const TaskModel = ({
project,
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 TaskModel = ({ project, onSubmit, onClose }) => {
const { activities, loading: activityLoading } = useActivitiesMaster();
const { categories, categoryLoading } = useWorkCategoriesMaster();
const {
register,
handleSubmit,
formState: { errors },
reset,
watch,
setValue,
reset,
formState: { errors },
} = useForm({
resolver: zodResolver(taskSchema),
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(() => {
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(() => {
if (!loading && Array.isArray(activities) && activities.length > 0) {
if (Array.isArray(activities) && activities.length > 0) {
setActivityData(activities);
}
}, [activities, loading]);
}, [activities]);
useEffect(() => {
if (
!categoryLoading &&
Array.isArray(categories) &&
categories.length > 0
) {
const newCategories = categories?.slice()?.sort((a, b) => {
const nameA = a?.name || "";
const nameB = b?.name || "";
return nameA?.localeCompare(nameB);
});
setCategoryData(newCategories);
setSelectedCategory(newCategories[0])
if (Array.isArray(categories) && categories.length > 0) {
const sorted = [...categories].sort((a, b) =>
(a?.name || "").localeCompare(b?.name || "")
);
setCategoryData(sorted);
setValue("workCategoryId", sorted?.[0]?.id?.toString() ?? "");
}
}, [categories, categoryLoading]);
}, [categories]);
const onSubmitForm = async (data) => {
const payload = [data];
CreateTask(payload);
};
return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
<div className="modal-content">
<div className="modal-body">
<div className="row">
<button
type="button"
className="btn-close"
aria-label="Close"
onClick={onClose}
/>
<div className="text-center mb-1">
<h5 className="mb-1">Manage 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>
<form className="row g-2" onSubmit={handleSubmit(onSubmitForm)}>
<div className="text-center mb-1">
<h5 className="mb-1">Manage Task</h5>
</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 WorkItem from "./WorkItem";
import { useProjectDetails } from "../../../hooks/useProjects";
import { cacheData, getCachedData } from "../../../slices/apiDataManager";
import { useDispatch } from "react-redux";
import { useProjectDetails, useProjectTasks } from "../../../hooks/useProjects";
import { cacheData } from "../../../slices/apiDataManager";
import { useDispatch, useSelector } from "react-redux";
import { refreshData } from "../../../slices/localVariablesSlice";
import ProjectRepository from "../../../repositories/ProjectRepository";
import showToast from "../../../services/toastService";
@ -13,216 +13,122 @@ import {
MANAGE_TASK,
} from "../../../utils/constants";
import { useParams } from "react-router-dom";
import ProgressDonutChart from "../../Charts/ProgressDonutChart";
import ProgressBar from "../../common/ProgressBar";
import { componentsToColor } from "pdf-lib";
import {formatNumber} from "../../../utils/dateUtils";
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 [Project, setProject] = useState();
const { projectId } = useParams();
const ManageTasks = useHasUserPermission(MANAGE_TASK);
const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
const ManageAndAssignTak = useHasUserPermission(ASSIGN_REPORT_TASK);
const { ProjectTaskList, isLoading } = useProjectTasks(workArea.id, IsExpandedArea);
const [workAreaStatus, setWorkAreaStatus] = useState({
completed: 0,
planned: 100,
});
useEffect(() => {
const totalCompleted = workItems.reduce(
(sum, i) => sum + (i.workItem?.completedWork || 0),
setProject(projects_Details);
}, [projects_Details]);
useEffect(() => {
const totalCompleted = ProjectTaskList?.reduce(
(sum, i) => sum + (i?.workItem?.completedWork || 0),
0
);
const totalPlanned = workItems.reduce(
(sum, i) => sum + (i.workItem?.plannedWork || 0),
const totalPlanned = ProjectTaskList?.reduce(
(sum, i) => sum + (i?.workItem?.plannedWork || 0),
0
);
const percent =
totalPlanned > 0 ? (totalCompleted / totalPlanned) * 100 : 0;
//setPercentComplete(Math.min(percent, 100)); // cap at 100%
setWorkAreaStatus({ completed: totalCompleted, planned: totalPlanned });
}, [workItems]);
}, [ProjectTaskList]);
useEffect(() => {
const project = getCachedData("projectInfo");
setProject(project);
const collapseElement = document.getElementById(`collapse-${workArea.id}`);
if (!project || !forBuilding?.id || !floor?.id || !workArea?.id) return;
const building = project.buildings?.find((b) => b.id === forBuilding.id);
const floors = building?.floors?.find((f) => f.id === floor.id);
const workAreas = floor?.workAreas?.find((wa) => wa.id === workArea.id);
setWorkItems(workAreas?.workItems || []);
}, [workArea, floor, floor]);
const handleShown = () => setIsExpandedArea(true);
const handleHidden = () => setIsExpandedArea(false);
const HanldeDeleteActivity = async (workItemId) => {
try {
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
});
});
collapseElement?.addEventListener("shown.bs.collapse", handleShown);
collapseElement?.addEventListener("hidden.bs.collapse", handleHidden);
return () => {
toggleButtons.forEach((btn) => {
btn.removeEventListener("click", () => {});
});
collapseElement?.removeEventListener("shown.bs.collapse", handleShown);
collapseElement?.removeEventListener("hidden.bs.collapse", handleHidden);
};
}, []);
}, [workArea.id]);
return (
<React.Fragment key={workArea.id}>
<tr>
<td colSpan="4" className="p-0">
<div
className="accordion border-none"
id={`accordion-${workArea.id}`}
>
<div className="accordion border-none" id={`accordion-${workArea.id}`}>
<div className="accordion-item background border-0">
{/* Accordion Header */}
<p
className="accordion-header mb-0"
id={`heading-${workArea.id}`}
>
<p className="accordion-header mb-0" id={`heading-${workArea.id}`}>
<button
className={`accordion-button text-start px-2 py-2 custom-accordion-btn ${
workItems && workItems.length > 0 ? "collapsed" : "disabled"
}`}
className="accordion-button text-start px-2 py-2 custom-accordion-btn collapsed"
type="button"
data-bs-toggle={
workItems && workItems.length > 0 ? "collapse" : ""
}
data-bs-target={
workItems && workItems.length > 0
? `#collapse-${workArea.id}`
: undefined
}
data-bs-toggle="collapse"
data-bs-target={`#collapse-${workArea.id}`}
aria-expanded="false"
aria-controls={`collapse-${workArea.id}`}
disabled={!(workItems && workItems.length > 0)}
>
<i
className={`bx me-2 toggle-icon ${
workItems && workItems.length > 0
? "bx-plus-circle"
: "bx-block"
IsExpandedArea ? "bx-minus-circle" : "bx-plus-circle"
}`}
style={{
fontSize: "1.2rem",
color:
workItems && workItems.length > 0 ? "" : "transparent",
color: "black",
}}
></i>
<div className="d-flex justify-content-start row w-100 align-items-center">
<div className="d-flex col-5">
<span className="fw-semibold text-primary small">
Floor:
</span>
<span className="fw-normal text-darkgreen small px-2">
<span className="fw-semibold text-primary small">Floor:</span>
<span className="fw-normal text-darkgreen small px-2">
{floor.floorName}
</span>
</div>
<div className="text-start col-5">
<span className="fw-semibold text-primary small">
Work Area:
</span>
<span className="fw-normal text-darkgreen small px-2">
<span className="fw-semibold text-primary small">Work Area:</span>
<span className="fw-normal text-darkgreen small px-2">
{workArea.areaName}
</span>
</div>
{workArea?.workItems?.length > 0 && (
<div className="col-2">
<ProgressBar
completedWork={workAreaStatus.completed}
plannedWork={workAreaStatus.planned}
completedWork={formatNumber(workArea?.completedWork)}
plannedWork={formatNumber(workArea?.plannedWork)}
className="m-0 text-info"
></ProgressBar>
/>
</div>
)}
</div>
</button>
</p>
{/* Accordion Body */}
{workItems && workItems.length > 0 && (
<div
id={`collapse-${workArea.id}`}
className="accordion-collapse collapse"
aria-labelledby={`heading-${workArea.id}`}
>
<div className="accordion-body px-1">
<div
id={`collapse-${workArea.id}`}
className="accordion-collapse collapse"
aria-labelledby={`heading-${workArea.id}`}
>
<div className="accordion-body px-1">
{isLoading ? (
<div className="text-center py-2 text-muted">Loading activities...</div>
) : ProjectTaskList?.length > 0 ? (
<table className="table table-sm mx-1">
<thead>
<tr>
<th className="infra-activity-table-header-first">
Activity
</th>
<th className="infra-activity-table-header-first">Activity</th>
<th className="infra-activity-table-header d-sm-table-cell d-md-none">
Status
</th>
@ -235,11 +141,8 @@ const WorkArea = ({ workArea, floor, forBuilding }) => {
<th className="infra-activity-table-header d-none d-md-table-cell">
Today's Planned
</th>
<th className="infra-activity-table-header">
Progress
</th>
{(ManageInfra ||
(!projectId && ManageAndAssignTak)) && (
<th className="infra-activity-table-header">Progress</th>
{(ManageInfra || (!projectId && ManageAndAssignTak)) && (
<th className="infra-activity-table-header text-end">
<span className="px-2">Actions</span>
</th>
@ -247,21 +150,24 @@ const WorkArea = ({ workArea, floor, forBuilding }) => {
</tr>
</thead>
<tbody className="table-border-bottom-0">
{workArea.workItems.map((workItem) => (
{ProjectTaskList.map((workItem,index) => (
<WorkItem
key={workItem.workItemId}
key={workItem.workItemId || `fallback-${index}`}
workItem={workItem}
forBuilding={forBuilding}
forFloor={floor}
forWorkArea={workArea}
deleteHandleTask={HanldeDeleteActivity}
/>
))}
</tbody>
</table>
</div>
) : (
<div className="text-center text-muted py-3">
No activities available for this work area.
</div>
)}
</div>
)}
</div>
</div>
</div>
</td>
@ -269,4 +175,5 @@ const WorkArea = ({ workArea, floor, forBuilding }) => {
</React.Fragment>
);
};
export default WorkArea;

View File

@ -1,39 +1,31 @@
import React, { useState, useEffect } from "react";
import { set, useForm } from "react-hook-form";
import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import showToast from "../../../services/toastService";
import { useManageProjectInfra } from "../../../hooks/useProjects";
import { useSelector } from "react-redux";
// Zod schema for form validation
const workAreaSchema = z.object({
id: z.string().nonempty("Floor is required"),
buildingId: z.string().nonempty("Building is required"),
floorId: z.string().nonempty("Floor is required"),
areaName: z
.string()
.nonempty("Work Area Name is required")
.min(3, "Name must be at least 3 characters long"),
id: z.string().optional(),
buildingId: z.string().refine((val) => val !== "0", {
message: "Building is required",
}),
floorId: z.string().refine((val)=>val !== "0",{message:"Floor is required"}),
areaName: z.string().min(3, "Work Area Name must be at least 3 characters"),
});
// Default form data
const defaultModel = {
id: "0",
areaName: "",
buildingId: "0",
floorId: "0",
areaName: "",
};
const WorkAreaModel = ({
project,
onSubmit,
clearTrigger,
onClearComplete,
onClose,
}) => {
const WorkAreaModel = ({ project, onSubmit, onClose }) => {
const [selectedBuilding, setSelectedBuilding] = useState(null);
const [selectedFloor, setSelectedFloor] = useState(null);
const [selectdWorkArea, setWorkArea] = useState();
const selectedProject = useSelector((store)=>store.localVariables.projectId)
const {
register,
handleSubmit,
@ -42,259 +34,168 @@ const WorkAreaModel = ({
reset,
watch,
} = useForm({
resolver: zodResolver(workAreaSchema), // Use Zod resolver for validation
resolver: zodResolver(workAreaSchema),
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(() => {
if (clearTrigger) {
reset(defaultModel); // Reset form to initial state
setSelectedBuilding(null);
setSelectedFloor(null);
onClearComplete();
}
}, [clearTrigger, onClearComplete, reset]);
const building = project?.find((b) => b.id === watchBuildingId);
setSelectedBuilding(building || null);
const handleWorkAreaChange = (e) => {
const { value } = e.target;
if (value === "0") {
setValue("id", "0"); // Create New Work Area
if (building) {
const floor = building.floors?.find((f) => f.id === watchFloorId);
setSelectedFloor(floor || null);
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 {
setSelectedFloor(null);
setValue("floorId", "0");
setValue("id", "0"); // Reset Work Area ID
setValue("areaName", ""); // Reset Work Area Name
setValue("areaName", "");
}
}, [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) => {
const { value } = e.target;
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
};
useEffect(() => {
reset(defaultModel);
}, []);
const onSubmitForm = (data) => {
let WorkArea = {
id: data.id == "0" ? null : data.id,
const onSubmitForm = ( data ) =>
{
const payload = {
id: data.id === "0" ? null : data.id,
areaName: data.areaName,
floorId: data.floorId,
buildingId: data.buildingId,
};
onSubmit(WorkArea);
let infraObject = [
{
building: null,
floor: null,
workArea: payload,
},
];
reset({
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();
ManageWorkArea({ infraObject, projectId: selectedProject });
};
return (
<div className="modal-dialog modal-lg modal-simple modal-edit-user">
<div className="modal-content">
<div className="modal-body">
<div className="row">
<button
type="button"
className="btn-close"
aria-label="Close"
onClick={onClose}
/>
<div className="text-center mb-1">
<h5 className="mb-1">Manage 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>
<form className="row g-2 p-2 p-md-1" onSubmit={handleSubmit(onSubmitForm)}>
<div className="text-center mb-1">
<h5 className="mb-1">Manage Work Area</h5>
</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";
import ConfirmModal from "../../common/ConfirmModal";
import ProjectRepository from "../../../repositories/ProjectRepository";
import { useProjectDetails } from "../../../hooks/useProjects";
import { useDeleteProjectTask, useProjectDetails } from "../../../hooks/useProjects";
import showToast from "../../../services/toastService";
import {
cacheData,
clearCacheKey,
getCachedData,
} from "../../../slices/apiDataManager";
import { useDispatch } from "react-redux";
import { refreshData } from "../../../slices/localVariablesSlice";
import GlobalModel from "../../common/GlobalModel";
import {useDeleteMasterItem} from "../../../hooks/masterHook/useMaster";
const WorkItem = ({
workItem,
@ -39,18 +39,24 @@ const WorkItem = ({
const ManageAndAssignTak = useHasUserPermission(ASSIGN_REPORT_TASK);
const [loadingDelete, setLoadingDelete] = useState(false);
const project = getCachedData("projectInfo");
const dispatch = useDispatch();
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
const closeModal = () => setIsModalOpen( false );
const showModalDelete = () => setShowModal2(true);
const closeModalDelete = () => setShowModal2(false);
const getProgress = (planned, completed) => {
return (completed * 100) / planned + "%";
};
const {mutate:DeleteTask,isPending } = useDeleteProjectTask(() => {
closeModalDelete?.();
});
const handleAssignTask = () => {
setItemName("");
};
useEffect(() => {
setNewWorkItem(workItem);
}, [workItem]);
@ -79,17 +85,15 @@ const WorkItem = ({
tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el));
}, []);
const showModal1 = () => setShowModal(true);
const closeModal1 = () => setShowModal(false);
const showModalDelete = () => setShowModal2(true);
const closeModalDelete = () => setShowModal2(false);
const handleSubmit = async () => {
setLoadingDelete(true);
let WorkItemId = workItem.workItemId || workItem.id;
deleteHandleTask(WorkItemId);
setLoadingDelete(false);
closeModalDelete();
DeleteTask({workItemId:WorkItemId,workAreaId:forWorkArea?.id})
};
const PlannedWork =
@ -105,21 +109,15 @@ const WorkItem = ({
)}
{showModal && (
<div
className={`modal fade ${showModal ? "show" : ""}`}
tabIndex="-1"
role="dialog"
style={{ display: showModal ? "block" : "none" }}
aria-hidden={!showModal}
>
<GlobalModel isOpen={showModal} size="lg" closeModal={()=>setShowModal(false)}>
<EditActivityModal
workItem={workItem}
workArea={forWorkArea}
building={forBuilding}
floor={forFloor}
onClose={closeModal1}
onClose={()=>setShowModal(false)}
/>
</div>
</GlobalModel>
)}
{showModal2 && (
@ -169,7 +167,7 @@ const WorkItem = ({
: "NA"}
</td>
{/* Category - visible on medium and above */}
<td className="text-center table-cell-small d-none d-md-table-cell">
<span className="fw-light">
{hasWorkItem
@ -204,7 +202,6 @@ const WorkItem = ({
: "NA"}
</td>
{/* Progress Bar - always visible */}
<td className="text-center " style={{ width: "15%" }}>
<div className="progress p-0">
<div
@ -231,7 +228,6 @@ const WorkItem = ({
</div>
</td>
{/* Actions - always visible */}
{(ManageInfra ||
(!projectId &&
ManageAndAssignTak &&
@ -255,7 +251,7 @@ const WorkItem = ({
<i
className="bx bxs-edit text-secondary cursor-pointer"
title="Edit"
onClick={showModal1}
onClick={()=>setShowModal(true)}
role="button"
></i>
<i
@ -297,7 +293,7 @@ const WorkItem = ({
<li>
<a
className="dropdown-item d-flex align-items-center"
onClick={showModal1}
onClick={()=>setShowModal(true) }
>
<i className="bx bxs-edit text-secondary me-2"></i> Edit
</a>

View File

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

View File

@ -1,8 +1,8 @@
import React, { useEffect, useState } from "react";
import moment from "moment";
import { getDateDifferenceInDays } from "../../utils/dateUtils";
import { formatNumber, getDateDifferenceInDays } from "../../utils/dateUtils";
import { useNavigate } from "react-router-dom";
import { useProjectDetails } from "../../hooks/useProjects";
import { useProjectDetails, useUpdateProject } from "../../hooks/useProjects";
import ManageProjectInfo from "./ManageProjectInfo";
import ProjectRepository from "../../repositories/ProjectRepository";
import { cacheData, getCachedData } from "../../slices/apiDataManager";
@ -13,29 +13,36 @@ import {
getProjectStatusColor,
getProjectStatusName,
} from "../../utils/projectStatus";
import GlobalModel from "../common/GlobalModel";
const ProjectCard = ({ projectData, recall }) => {
const [projectInfo, setProjectInfo] = useState(projectData);
const [projectDetails, setProjectDetails] = useState(null);
const [ projectInfo, setProjectInfo ] = useState( projectData );
const { projects_Details, loading, error, refetch } = useProjectDetails(
projectInfo?.id,false
);
const [showModal, setShowModal] = useState(false);
const navigate = useNavigate();
const ManageProject = useHasUserPermission(MANAGE_PROJECT);
const [modifyProjectLoading, setMdifyProjectLoading] = useState(false);
const {
mutate: updateProject,
isPending,
isSuccess,
isError,
} = useUpdateProject({
onSuccessCallback: () => {
setShowModal(false);
},
})
useEffect(()=>{
setProjectInfo(projectData);
},[projectData])
// console.log("in card view",projectInfo);
const handleShow = async () => {
}, [ projectData ] )
const handleShow = async () => {
try {
setMdifyProjectLoading(true);
const response = await ProjectRepository.getProjectByprojectId(
projectInfo.id
);
setProjectDetails(response.data);
setMdifyProjectLoading(false);
const { data } = await refetch();
setShowModal(true);
} catch (error) {
} catch (err) {
showToast("Failed to load project details", "error");
}
};
@ -54,64 +61,30 @@ const ProjectCard = ({ projectData, recall }) => {
};
const handleFormSubmit = (updatedProject) => {
if (projectInfo?.id) {
ProjectRepository.updateProject(projectInfo.id, updatedProject)
.then((response) => {
const updatedProjectData = {
...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");
});
}
};
if (projectInfo?.id) {
updateProject({
projectId: projectInfo.id,
updatedData: updatedProject,
});
}
};
return (
<>
{showModal && projectDetails && (
<div
className="modal fade show"
tabIndex="-1"
role="dialog"
style={{ display: "block" }}
aria-hidden="false"
>
<ManageProjectInfo
project={projectDetails}
{showModal && projects_Details && (
<GlobalModel isOpen={showModal} closeModal={handleClose}>
<ManageProjectInfo
project={projects_Details}
handleSubmitForm={handleFormSubmit}
onClose={handleClose}
isPending={isPending}
/>
</div>
</GlobalModel>
)}
<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="d-flex align-items-start">
<div className="d-flex align-items-center">
@ -143,23 +116,23 @@ const ProjectCard = ({ projectData, recall }) => {
data-bs-toggle="dropdown"
aria-expanded="false"
>
{modifyProjectLoading ? (
<div
className="spinner-border spinner-border-sm text-secondary"
role="status"
>
<span className="visually-hidden">Loading...</span>
</div>
) : (
<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>
)}
{loading ? (
<div
className="spinner-border spinner-border-sm text-secondary"
role="status"
>
<span className="visually-hidden">Loading...</span>
</div>
) : (
<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>
<ul className="dropdown-menu dropdown-menu-end">
<li>
@ -251,7 +224,7 @@ const ProjectCard = ({ projectData, recall }) => {
</div>
<div className="d-flex justify-content-between align-items-center mb-2">
<small className="text-body">
Task: {projectInfo.completedWork} / {projectInfo.plannedWork}
Task: {formatNumber(projectInfo.completedWork)} / {formatNumber(projectInfo.plannedWork)}
</small>
<small className="text-body">
{Math.floor(

View File

@ -8,7 +8,6 @@ import TaskModel from "./Infrastructure/TaskModel";
import ProjectRepository, {
TasksRepository,
} from "../../repositories/ProjectRepository";
import ProjectModal from "./ProjectModal";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { MANAGE_PROJECT_INFRA } from "../../utils/constants";
import InfraTable from "./Infrastructure/InfraTable";
@ -17,334 +16,38 @@ import {
clearCacheKey,
getCachedData,
} from "../../slices/apiDataManager";
import { useProjectDetails } from "../../hooks/useProjects";
import { useProjectDetails, useProjectInfra } from "../../hooks/useProjects";
import { useDispatch, useSelector } from "react-redux";
import { refreshData } from "../../slices/localVariablesSlice";
import eventBus from "../../services/eventBus";
import {useParams} from "react-router-dom";
import GlobalModel from "../common/GlobalModel";
const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
const ProjectInfra = ( {data, onDataChange, eachSiteEngineer} ) =>
{
const {projectId} = useParams()
const reloadedData = useSelector((store) => store.localVariables.reload);
const [expandedBuildings, setExpandedBuildings] = useState([]);
const [ expandedBuildings, setExpandedBuildings ] = useState( [] );
const {projectInfra,isLoading,error} = useProjectInfra(projectId)
const { projects_Details, refetch, loading } = useProjectDetails(data?.id);
const [project, setProject] = useState(projects_Details);
const [modalConfig, setModalConfig] = useState({ type: null, data: null });
const [isModalOpen, setIsModalOpen] = useState(false);
const [ project, setProject ] = useState( projects_Details );
const ManageInfra = useHasUserPermission(MANAGE_PROJECT_INFRA);
const [isBuildingModalOpen, setIsBuildingModalOpen] = useState(false);
const [isFloorModalOpen, setIsFloorModalOpen] = useState(false);
const [isWorkAreaModelOpen, setIsWorkAreaModalOpen] = useState(false);
const [isTaskModelOpen, setIsTaskModalOpen] = useState(false);
const [isAssignRoleModal, setIsAssingRoleModal] = useState(false);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [clearFormTrigger, setClearFormTrigger] = useState(false);
const [CurrentBuilding, setCurrentBuilding] = useState("");
const [showModal, setShowModal] = useState(false);
const [showModalFloor, setshowModalFloor] = useState(false);
const [showModalWorkArea, setshowModalWorkArea] = useState(false);
const [showModalTask, setshowModalTask] = useState(false);
const [showModalBuilding, setshowModalBuilding] = useState(false);
const dispatch = useDispatch();
useEffect(() => {
setProject(projects_Details);
setProject(projectInfra);
}, [data, projects_Details]);
const openFloorModel = (projectData) => {
setIsFloorModalOpen(true);
};
const closeFloorModel = () => {
setIsFloorModalOpen(false);
};
const openAssignModel = (assignData) => {
setCurrentBuilding(assignData);
setIsAssingRoleModal(true);
};
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]);
// useEffect(() => {
// if (reloadedData) {
// refetch();
// dispatch(refreshData(false));
// }
// }, [reloadedData]);
const signalRHandler = (response) => {
setProject(response);
@ -352,82 +55,30 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
return (
<>
<div
className={`modal fade ${showModal ? "show" : ""}`}
tabIndex="-1"
role="dialog"
style={{ display: showModal ? "block" : "none" }}
aria-hidden={!showModal}
>
{showModalBuilding && <GlobalModel isOpen={showModalBuilding} size="md" closeModal={() => setshowModalBuilding( false )}>
<BuildingModel
project={project}
onClose={handleClose}
onSubmit={handleBuildingModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
></BuildingModel>
</div>
{isFloorModalOpen && (
<div
className="modal fade show"
id="floor-model"
tabIndex="-1"
role="dialog"
style={{ display: "block" }}
aria-hidden="false"
>
<FloorModel
project={project}
onClose={closeFloorModel}
onSubmit={handleFloorModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
project={projectInfra}
onClose={() => setshowModalBuilding( false )}
/>
</GlobalModel>}
{showModalFloor && <GlobalModel isOpen={showModalFloor} size="md" closeModal={()=>setshowModalFloor(false)}>
<FloorModel
project={projectInfra}
onClose={()=>setshowModalFloor(false)}
/>
</div>
)}
{isWorkAreaModelOpen && (
<div
className="modal fade show"
id="work-area-model"
tabIndex="-1"
role="dialog"
style={{ display: "block" }}
aria-hidden="false"
>
</GlobalModel>}
{showModalWorkArea && <GlobalModel isOpen={showModalWorkArea} size="lg" closeModal={()=>setshowModalWorkArea(false)} >
<WorkAreaModel
project={project}
onClose={closeWorkAreaModel}
onSubmit={handleWorkAreaModelFormSubmit}
clearTrigger={clearFormTrigger}
onClearComplete={() => setClearFormTrigger(false)}
project={projectInfra}
onClose={()=>setshowModalWorkArea(false)}
/>
</div>
)}
{isTaskModelOpen && (
<div
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)}
</GlobalModel>}
{showModalTask && ( <GlobalModel isOpen={showModalTask} size="lg" closeModal={()=>setshowModalTask(false)}>
<TaskModel
project={projectInfra}
onClose={()=>setshowModalTask(false)}
/>
</div>
)}
{isModalOpen && (
<ProjectModal modalConfig={modalConfig} closeModal={closeModal} />
)}
</GlobalModel>)}
<div className="col-md-12 col-lg-12 col-xl-12 order-0 mb-4">
<div className="card">
<div className="card-body" style={{ padding: "0.5rem" }}>
@ -441,15 +92,15 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
<button
type="button"
className="link-button link-button-sm m-1 "
onClick={handleShow}
onClick={()=>setshowModalBuilding(true)}
>
<i className="bx bx-plus-circle me-2"></i>
<i className="bx bx-plus-circle me-2"></i>
Manage Building
</button>
<button
type="button"
className="link-button m-1"
onClick={() => openFloorModel()}
onClick={()=>setshowModalFloor(true)}
>
<i className="bx bx-plus-circle me-2"></i>
Manage Floors
@ -457,7 +108,7 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
<button
type="button"
className="link-button m-1"
onClick={() => openWorkAreaModel()}
onClick={() => setshowModalWorkArea(true)}
>
<i className="bx bx-plus-circle me-2"></i>
Manage Work Areas
@ -465,7 +116,7 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
<button
type="button"
className="link-button m-1"
onClick={() => openTaskModel()}
onClick={()=>setshowModalTask(true)}
>
<i className="bx bx-plus-circle me-2"></i>
Create Tasks
@ -473,15 +124,16 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
</div>
</div>
<div className="row ">
{loading && <p>Loading....</p>}
{project && project.buildings?.length > 0 && (
{isLoading && <p>Loading....</p>}
{projectInfra && projectInfra?.length > 0 && (
<InfraTable
buildings={project?.buildings}
projectId={project.id}
handleFloor={submitData}
signalRHandler = {signalRHandler}
buildings={projectInfra}
projectId={projectId}
// handleFloor={submitData}
// signalRHandler ={signalRHandler}
/>
)}
{!isLoading && projectInfra?.length == 0 && <div className="mt-5"><p>No Infra Avaiable</p></div>}
</div>
</div>
</div>

View File

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

View File

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

View File

@ -3,61 +3,14 @@ import {
useEmployeesByProjectAllocated,
useProjects,
} from "../../hooks/useProjects";
import { formatNumber } from "../../utils/dateUtils";
import ProgressBar from "../common/ProgressBar";
const ProjectOverview = ({ project }) => {
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);
// 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 (
<div className="card mb-6">
<div className="card-header text-start">
@ -72,12 +25,12 @@ const ProjectOverview = ({ project }) => {
<li className="d-flex align-items-center mb-3">
<i className="bx bx-check"></i>
<span className="fw-medium mx-2">Task Planned:</span>{" "}
<span>{project_detail?.plannedWork}</span>
<span>{formatNumber(project_detail?.plannedWork)}</span>
</li>
<li className="d-flex align-items-center mb-3">
<i className="bx bx-star"></i>
<span className="fw-medium mx-2">Task Completed:</span>{" "}
<span>{project_detail?.completedWork}</span>
<span>{formatNumber(project_detail?.completedWork)}</span>
</li>
<li className="d-flex align-items-center mb-3">
<i className="bx bx-user"></i>
@ -89,16 +42,21 @@ const ProjectOverview = ({ project }) => {
<>
<div className="d-flex text-end mb-2 mt-5">
<small className="text-body text-muted ">
{Math.floor(
{/* {Math.floor(
getProgressInNumber(
project_detail.plannedWork,
project_detail.completedWork
)
) || 0}{" "}
) || 0}{" "} */}
{
(formatNumber(project_detail.plannedWork),
"/",
formatNumber(project_detail.completedWork))
}
% Completed
</small>
</div>
<div
{/* <div
className="progress mb-4 rounded"
style={{ height: "8px" }}
>
@ -115,7 +73,12 @@ const ProjectOverview = ({ project }) => {
aria-valuemin="0"
aria-valuemax={project_detail.plannedWork}
></div>
</div>
</div> */}
<ProgressBar
completedWork={formatNumber(project_detail?.completedWork)}
plannedWork={formatNumber(project_detail?.plannedWork)}
className="m-0 text-info"
/>
</>
)}
</li>

View File

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

View File

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

View File

@ -7,6 +7,7 @@ import { MasterRespository } from "../../repositories/MastersRepository";
import { clearApiCacheKey } from "../../slices/apiCacheSlice";
import { getCachedData, cacheData } from "../../slices/apiDataManager";
import showToast from "../../services/toastService";
import {useCreateActivity} from "../../hooks/masterHook/useMaster";
const schema = z.object({
activityName: z.string().min(1, { message: "Activity Name is required" }),
@ -23,101 +24,96 @@ const schema = z.object({
});
const CreateActivity = ({ onClose }) => {
const [isLoading, setIsLoading] = useState(false);
const maxDescriptionLength = 255;
const { mutate: createActivity, isPending: isLoading } = useCreateActivity(() => onClose?.());
const {
register,
handleSubmit,
control,
setValue,
clearErrors,
setError,
getValues,
reset,
formState: { errors },
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
activityName: "",
unitOfMeasurement: "",
checkList: [],
},
});
const {
register,
handleSubmit,
control,
setValue,
clearErrors,
setError,
getValues,
reset,
formState: { errors },
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
activityName: "",
unitOfMeasurement: "",
checkList: [],
},
});
const {
fields: checkListItems,
append,
remove,
} = useFieldArray({
control,
name: "checkList",
});
const {
fields: checkListItems,
append,
remove,
} = useFieldArray({
control,
name: "checkList",
});
const addChecklistItem = useCallback(() => {
const values = getValues("checkList");
const lastIndex = checkListItems.length - 1;
// Form submission handler
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 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,
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;
}
const removeChecklistItem = (index) => {
remove(index);
};
clearErrors(`checkList.${lastIndex}.description`);
append({ id: null, description: "", isMandatory: false });
}, [checkListItems, getValues, append, setError, clearErrors]);
const removeChecklistItem = useCallback((index) => {
remove(index);
}, [remove]);
const handleChecklistChange = (index, value) => {
setValue(`checkList.${index}`, value);
};
const handleChecklistChange = useCallback((index, value) => {
setValue(`checkList.${index}`, value);
}, [setValue]);
const handleClose = () => {
reset();
onClose();
};
const onSubmit = (formData) => {
createActivity(formData);
};
// 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 (
<form onSubmit={handleSubmit(onSubmit)}>
{/* <h6>Create Activity</h6> */}

View File

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

View File

@ -6,6 +6,7 @@ import { MasterRespository } from '../../repositories/MastersRepository';
import { clearApiCacheKey } from '../../slices/apiCacheSlice';
import { getCachedData,cacheData } from '../../slices/apiDataManager';
import showToast from '../../services/toastService';
import {useCreateContactTag} from '../../hooks/masterHook/useMaster';
const schema = z.object({
@ -15,53 +16,53 @@ const schema = z.object({
});
const CreateContactTag = ({onClose}) => {
const {
register,
handleSubmit,
formState: { errors },
reset,
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
name: "",
description: "",
},
});
const[isLoading,setIsLoading] = useState(false)
const {
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);
}
const [descriptionLength, setDescriptionLength] = useState(0);
const maxDescriptionLength = 255;
useEffect(()=>{
return ()=>resetForm()
},[])
const [descriptionLength, setDescriptionLength] = useState(0);
const maxDescriptionLength = 255;
const { mutate: createContactTag, isPending: isLoading } = useCreateContactTag(() => onClose?.());
const onSubmit = (payload) => {
createContactTag(payload);
};
// 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 (<>
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
<div className="col-12 col-md-12">

View File

@ -6,6 +6,7 @@ import { MasterRespository } from '../../repositories/MastersRepository';
import { clearApiCacheKey } from '../../slices/apiCacheSlice';
import { getCachedData,cacheData } from '../../slices/apiDataManager';
import showToast from '../../services/toastService';
import {useCreateJobRole} from '../../hooks/masterHook/useMaster';
const schema = z.object({
@ -16,58 +17,75 @@ const schema = z.object({
const CreateJobRole = ({onClose}) => {
const[isLoading,setIsLoading] = useState(false)
const {
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");
const maxDescriptionLength = 255;
const [descriptionLength, setDescriptionLength] = useState(0);
onClose()
}).catch((error)=>{
showToast(error.message, "error");
setIsLoading(false)
})
const {
register,
handleSubmit,
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 = () => {
reset({
role: "",
description: ""
});
setDescriptionLength(0);
}
createJobRole(payload);
};
useEffect(() => {
const sub = watch((values) => {
setDescriptionLength(values.description?.length || 0);
});
return () => sub.unsubscribe();
}, [watch]);
useEffect(() => {
return () => resetForm();
}, []);
useEffect(()=>{
return ()=>resetForm()
},[])
const [descriptionLength, setDescriptionLength] = useState(0);
const maxDescriptionLength = 255;
return (<>
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
{/* <div className="col-12 col-md-12">

View File

@ -1,21 +1,19 @@
import React, { useEffect, useState, useRef } from "react";
import { useFeatures } from "../../hooks/useMasterRole";
import { useForm } from 'react-hook-form';
import { set, z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from "react-hook-form";
import { set, z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { MasterRespository } from "../../repositories/MastersRepository";
import { cacheData, getCachedData } from "../../slices/apiDataManager";
import showToast from "../../services/toastService";
import { useCreateApplicationRole } from "../../hooks/masterHook/useMaster";
const schema = z.object({
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" }),
selectedPermissions: z
@ -23,21 +21,22 @@ const schema = z.object({
.min(1, { message: "Please select at least one permission" }),
});
const CreateRole = ({ modalType, onClose }) => {
const [isLoading, setIsLoading] = useState(false)
const maxDescriptionLength = 255;
const [descriptionLength, setDescriptionLength] = useState(0);
const popoverRefs = useRef([]);
const { masterFeatures } = useFeatures()
const { masterFeatures } = useFeatures();
const { mutate: submitNewApplicationRole, isPending: isLoading } =
useCreateApplicationRole(() => {
onClose?.();
});
const {
register,
handleSubmit,
formState: { errors },
reset,
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
@ -48,9 +47,7 @@ const CreateRole = ({ modalType, onClose }) => {
});
const onSubmit = (values) => {
setIsLoading(true)
const result = {
const payload = {
role: values.role,
description: values.description,
featuresPermission: values.selectedPermissions.map((id) => ({
@ -59,27 +56,40 @@ const CreateRole = ({ modalType, onClose }) => {
})),
};
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()
})
submitNewApplicationRole(payload);
};
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(() => {
setDescriptionLength(0);
}, []);
useEffect(() => {
popoverRefs.current.forEach((el) => {
if (el) {
@ -87,14 +97,13 @@ const CreateRole = ({ modalType, onClose }) => {
trigger: "focus",
placement: "right",
html: true,
content: el.getAttribute("data-bs-content"), // use inline content from attribute
content: el.getAttribute("data-bs-content"),
});
}
});
}, [masterFeatures]);
return (
<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">Create Application Role</label>
@ -102,23 +111,24 @@ const CreateRole = ({ modalType, onClose }) => {
</div> */}
<div className="col-12 col-md-12">
<label className="form-label">Role</label>
<input type="text"
<input
type="text"
{...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>}
</div>
<div className="col-12 col-md-12">
<label className="form-label" htmlFor="description">Description</label>
<label className="form-label" htmlFor="description">
Description
</label>
<textarea
rows="3"
{...register("description")}
className={`form-control ${errors.description ? 'is-invalids' : ''}`}
className={`form-control ${errors.description ? "is-invalids" : ""}`}
onChange={(e) => {
setDescriptionLength(e.target.value.length);
// Also update react-hook-form's value
register("description").onChange(e);
}}
></textarea>
@ -131,26 +141,26 @@ const CreateRole = ({ modalType, onClose }) => {
)}
</div>
<div className="col-12 col-md-12">
<div className="col-12 col-md-12 border">
{masterFeatures.map((feature, featureIndex) => (
<React.Fragment key={feature.id}>
<div className="row my-1" key={feature.id} style={{ marginLeft: "0px" }}>
<div className="col-12 col-md-3 d-flex text-start align-items-start" style={{ wordWrap: 'break-word' }}>
<div
className="row my-1"
key={feature.id}
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>
</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">
{feature.featurePermissions.map((perm, permIndex) => {
const refIndex = (featureIndex * 10) + permIndex;
const refIndex = featureIndex * 10 + permIndex;
return (
<div className="d-flex me-3 mb-2" key={perm.id}>
<label className="form-check-label" htmlFor={perm.id}>
@ -163,15 +173,14 @@ const CreateRole = ({ modalType, onClose }) => {
/>
{perm.name}
</label>
<div style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ display: "flex", alignItems: "center" }}>
<div
key={refIndex}
ref={(el) =>
(popoverRefs.current[refIndex] = el)
}
ref={(el) => (popoverRefs.current[refIndex] = el)}
tabIndex="0"
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-placement="right"
data-bs-html="true"
@ -182,23 +191,26 @@ const CreateRole = ({ modalType, onClose }) => {
`}
>
&nbsp;
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="currentColor" className="bi bi-info-circle" viewBox="0 0 16 16">
<svg
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.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>
</div>
</div>
</div>
)
);
})}
</div>
</div>
<hr className="hr my-1 py-1" />
</React.Fragment>
))}
{errors.selectedPermissions && (
<p className="text-danger">{errors.selectedPermissions.message}</p>
@ -206,33 +218,23 @@ const CreateRole = ({ modalType, onClose }) => {
{!masterFeatures && <p>Loading...</p>}
</div>
{
masterFeatures && (
<div className="col-12 text-center">
<button type="submit" className="btn btn-sm btn-primary me-3">
{isLoading ? "Please Wait..." : "Submit"}
</button>
<button
type="reset"
className="btn btn-sm btn-label-secondary"
data-bs-dismiss="modal"
aria-label="Close"
>
Cancel
</button>
</div>
)
}
{masterFeatures && (
<div className="col-12 text-center">
<button type="submit" className="btn btn-sm btn-primary me-3">
{isLoading ? "Please Wait..." : "Submit"}
</button>
<button
type="reset"
className="btn btn-sm btn-label-secondary"
data-bs-dismiss="modal"
aria-label="Close"
>
Cancel
</button>
</div>
)}
</form>
);
};
export default CreateRole;

View File

@ -6,6 +6,7 @@ import { MasterRespository } from '../../repositories/MastersRepository';
import { clearApiCacheKey } from '../../slices/apiCacheSlice';
import { getCachedData,cacheData } from '../../slices/apiDataManager';
import showToast from '../../services/toastService';
import {useCreateWorkCategory} from '../../hooks/masterHook/useMaster';
const schema = z.object({
@ -16,58 +17,64 @@ const schema = z.object({
const CreateWorkCategory = ({onClose}) => {
const[isLoading,setIsLoading] = useState(false)
const {
register,
handleSubmit,
formState: { errors },reset
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
name: "",
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");
const {
register,
handleSubmit,
formState: { errors },
reset,
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
name: "",
description: "",
},
});
onClose()
}).catch((error)=>{
showToast(error?.response?.data?.message, "error");
setIsLoading(false)
})
const [descriptionLength, setDescriptionLength] = useState(0);
const maxDescriptionLength = 255;
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(()=>{
return ()=>resetForm()
},[])
const [descriptionLength, setDescriptionLength] = useState(0);
const maxDescriptionLength = 255;
const resetForm = () => {
reset({
name: "",
description: "",
});
setDescriptionLength(0);
};
useEffect(() => {
return () => resetForm();
}, []);
return (<>
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
<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 showToast from "../../services/toastService";
import {getCachedData,cacheData} from "../../slices/apiDataManager";
import {useUpdateActivity} from "../../hooks/masterHook/useMaster";
const schema = z.object({
@ -23,111 +24,107 @@ const schema = z.object({
const UpdateActivity = ({ activityData, onClose }) => {
const [isLoading, setIsLoading] = useState(false);
const { mutate: updateActivity, isPending: isLoading } = useUpdateActivity(() => onClose?.());
const {
register,
handleSubmit,
control,
setValue,
reset,
setError,
clearErrors,
getValues,
formState: { errors },
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
id:activityData.id,
activityName: activityData.activityName,
unitOfMeasurement: activityData.unitOfMeasurement,
checkLists: activityData.checkLists || [],
},
});
const {
register,
handleSubmit,
control,
setValue,
reset,
setError,
clearErrors,
getValues,
formState: { errors },
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
id: activityData?.id,
activityName: activityData?.activityName,
unitOfMeasurement: activityData?.unitOfMeasurement,
checkList: activityData?.checkLists || [],
},
});
const { fields: checkListItems, append, remove } = useFieldArray({
control,
name: "checkList",
});
const { fields: checkListItems, append, remove } = useFieldArray({
control,
name: "checkList",
});
// Load initial data
useEffect(() => {
if (activityData) {
reset( {
id:activityData.id,
activityName: activityData.activityName,
unitOfMeasurement: activityData.unitOfMeasurement,
checkList: activityData.checkLists || [],
});
}
}, [activityData]);
useEffect(() => {
if (activityData) {
reset({
id: activityData.id,
activityName: activityData.activityName,
unitOfMeasurement: activityData.unitOfMeasurement,
checkList: activityData.checkLists || [],
});
}
}, [activityData, reset]);
const handleChecklistChange = (index, value) => {
setValue(`checkList.${index}`, value);
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);
};
const handleChecklistChange = (index, value) => {
setValue(`checkList.${index}`, value);
};
const onSubmit = (formData) => {
const payload = { ...formData, id: activityData.id };
updateActivity({ id: activityData.id, payload });
};
// Submit handler
const onSubmit = async(data) => {
setIsLoading(true);
// const onSubmit = async(data) => {
// setIsLoading(true);
const Activity = {...data, id:activityData.id}
try
{
const response = await MasterRespository.updateActivity( activityData?.id, Activity );
const updatedActivity = response.data;
const cachedData = getCachedData("Activity")
// const Activity = {...data, id:activityData.id}
// try
// {
// const response = await MasterRespository.updateActivity( activityData?.id, Activity );
// const updatedActivity = response.data;
// const cachedData = getCachedData("Activity")
if (cachedData) {
const updatedActivities = cachedData.map((activity) =>
activity.id === updatedActivity.id ? { ...activity, ...updatedActivity } : activity
);
// if (cachedData) {
// const updatedActivities = cachedData.map((activity) =>
// activity.id === updatedActivity.id ? { ...activity, ...updatedActivity } : activity
// );
cacheData( "Activity", updatedActivities );
onClose()
}
setIsLoading( false )
showToast("Activity Successfully Updated", "success");
} catch ( err )
{
setIsLoading( false )
// cacheData( "Activity", updatedActivities );
// onClose()
// }
// setIsLoading( false )
// showToast("Activity Successfully Updated", "success");
// } catch ( err )
// {
// setIsLoading( false )
showToast("error.message", "error");
}
};
// showToast("error.message", "error");
// }
// };
// Add new checklist item
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));
}, []);
useEffect(() => {
const tooltipTriggerList = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el));
}, []);
return (
<form onSubmit={handleSubmit(onSubmit)}>
@ -232,7 +229,7 @@ const UpdateActivity = ({ activityData, onClose }) => {
className="btn btn-xs btn-primary mt-2"
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"
data-bs-original-title="Add check" ></i>
</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 { getCachedData,cacheData } from '../../slices/apiDataManager';
import showToast from '../../services/toastService';
import {useUpdateContactCategory} from '../../hooks/masterHook/useMaster';
const schema = z.object({
@ -15,65 +16,72 @@ const schema = z.object({
});
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 {
register,
handleSubmit,
formState: { errors },reset
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
name: data?.name || "",
description:data?.description || "",
},
});
const onSubmit = (formdata) => {
setIsLoading(true)
const result = {
id:data?.id,
name: formdata?.name,
description: formdata.description,
};
const [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
const maxDescriptionLength = 255;
const { mutate: updateContactCategory, isPending: isLoading } = useUpdateContactCategory(() => onClose?.());
const onSubmit = (formData) => {
const payload = {
id: data?.id,
name: formData.name,
description: formData.description,
};
updateContactCategory({id:data?.id,payload});
};
// const onSubmit = (formdata) => {
// setIsLoading(true)
// const result = {
// id:data?.id,
// name: formdata?.name,
// description: formdata.description,
// };
MasterRespository.updateContactCategory(data?.id,result).then((resp)=>{
setIsLoading(false)
showToast("Contact Category Updated successfully.", "success");
const cachedData = getCachedData("Contact Category");
if (cachedData) {
// MasterRespository.updateContactCategory(data?.id,result).then((resp)=>{
// setIsLoading(false)
// showToast("Contact Category Updated successfully.", "success");
// const cachedData = getCachedData("Contact Category");
// if (cachedData) {
const updatedData = cachedData.map((category) =>
category.id === data?.id ? { ...category, ...resp.data } : category
);
cacheData("Contact Category", updatedData);
}
// const updatedData = cachedData.map((category) =>
// category.id === data?.id ? { ...category, ...resp.data } : category
// );
// cacheData("Contact Category", updatedData);
// }
onClose()
}).catch((error)=>{
showToast(error?.response?.data?.message, "error")
setIsLoading(false)
})
// onClose()
// }).catch((error)=>{
// showToast(error?.response?.data?.message, "error")
// setIsLoading(false)
// })
};
const resetForm = () => {
reset({
name: "",
description: ""
});
setDescriptionLength(0);
}
// };
useEffect(()=>{
return ()=>resetForm()
},[])
const [descriptionLength, setDescriptionLength] = useState(0);
const maxDescriptionLength = 255;
const resetForm = () => {
reset({ name: "", description: "" });
setDescriptionLength(0);
};
useEffect(() => {
return () => resetForm();
}, []);
return (<>
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
<div className="col-12 col-md-12">

View File

@ -6,6 +6,7 @@ import { MasterRespository } from '../../repositories/MastersRepository';
import { clearApiCacheKey } from '../../slices/apiCacheSlice';
import { getCachedData,cacheData } from '../../slices/apiDataManager';
import showToast from '../../services/toastService';
import {useUpdateContactTag} from '../../hooks/masterHook/useMaster';
const schema = z.object({
@ -15,65 +16,72 @@ const schema = z.object({
});
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 {
register,
handleSubmit,
formState: { errors },reset
} = useForm({
resolver: zodResolver(schema),
defaultValues: {
name: data?.name || "",
description:data?.description || "",
},
});
const onSubmit = (formdata) => {
setIsLoading(true)
const result = {
id:data?.id,
name: formdata?.name,
description: formdata.description,
};
const [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
const maxDescriptionLength = 255;
const { mutate: updateContactTag, isPending: isLoading } = useUpdateContactTag(() => onClose?.());
const onSubmit = (formData) => {
const payload = {
id: data?.id,
name: formData.name,
description: formData.description,
};
debugger
updateContactTag({ id: data?.id, payload} );
}
// const onSubmit = (formdata) => {
// setIsLoading(true)
// const result = {
// id:data?.id,
// name: formdata?.name,
// description: formdata.description,
// };
MasterRespository.updateContactTag(data?.id,result).then((resp)=>{
setIsLoading(false)
showToast("Contact Tag Updated successfully.", "success");
const cachedData = getCachedData("Contact Tag");
if (cachedData) {
// MasterRespository.updateContactTag(data?.id,result).then((resp)=>{
// setIsLoading(false)
// showToast("Contact Tag Updated successfully.", "success");
// const cachedData = getCachedData("Contact Tag");
// if (cachedData) {
const updatedData = cachedData.map((category) =>
category.id === data?.id ? { ...category, ...resp.data } : category
);
cacheData("Contact Tag", updatedData);
}
// const updatedData = cachedData.map((category) =>
// category.id === data?.id ? { ...category, ...resp.data } : category
// );
// cacheData("Contact Tag", updatedData);
// }
onClose()
}).catch((error)=>{
showToast(error?.response?.data?.message, "error")
setIsLoading(false)
})
// onClose()
// }).catch((error)=>{
// showToast(error?.response?.data?.message, "error")
// setIsLoading(false)
// })
};
const resetForm = () => {
reset({
name: "",
description: ""
});
setDescriptionLength(0);
}
// };
useEffect(()=>{
return ()=>resetForm()
},[])
const [descriptionLength, setDescriptionLength] = useState(0);
const maxDescriptionLength = 255;
const resetForm = () => {
reset({ name: "", description: "" });
setDescriptionLength(0);
};
useEffect(() => {
return () => resetForm();
}, []);
return (<>
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
<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 { cacheData,getCachedData } from '../../slices/apiDataManager';
import showToast from '../../services/toastService';
import {useUpdateJobRole} from '../../hooks/masterHook/useMaster';
@ -17,63 +18,81 @@ const schema = z.object({
const EditJobRole = ({data,onClose}) => {
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 [descriptionLength, setDescriptionLength] = useState(data?.description?.length || 0);
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 (<>
<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 { cacheData, getCachedData } from "../../slices/apiDataManager";
import showToast from "../../services/toastService";
import {useUpdateApplicationRole} from "../../hooks/masterHook/useMaster";
@ -27,128 +28,149 @@ const updateSchema = z.object({
const EditMaster = ({ master, onClose }) => {
const [isLoading, setIsLoading] = useState(false)
const { masterFeatures } = useFeatures()
const popoverRefs = useRef([]);
const maxDescriptionLength = 255;
const popoverRefs = useRef([]);
const { masterFeatures } = useFeatures();
const { mutate: updateApplicationRole, isPending: isLoading } = useUpdateApplicationRole(() => onClose?.());
const buildDefaultPermissions = () => {
const defaults = {};
masterFeatures.forEach((feature) => {
feature.featurePermissions.forEach((perm) => {
const existing = master?.item?.featurePermission?.find(
(p) => p.id === perm.id
);
defaults[perm.id] = existing ? existing.isEnabled : false;
});
const buildDefaultPermissions = () => {
const defaults = {};
masterFeatures.forEach((feature) => {
feature.featurePermissions.forEach((perm) => {
const existing = master?.item?.featurePermission?.find(p => p.id === perm.id);
defaults[perm.id] = 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) => {
setIsLoading(true)
const existingIds = new Set(
master?.item?.featurePermission?.map((p) => p.id)
);
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,
},
});
const updatedPermissions = Object.entries(data.permissions)
.filter(([id, value]) => {
if (existingIds.has(id)) return true;
const [descriptionLength, setDescriptionLength] = useState(master?.item?.description?.length || 0);
return (
value === true ||
(dirtyFields.permissions && dirtyFields.permissions[id])
);
})
.map(([id, value]) => ({ id, isEnabled: value }));
const onSubmit = (data) => {
const existingIds = new Set(master?.item?.featurePermission?.map(p => p.id));
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)
const updatedPermissions = Object.entries(data.permissions)
.filter(([id, value]) => {
return existingIds.has(id) || value === true || (dirtyFields.permissions?.[id]);
})
.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(() => {
reset({
role: master?.item?.role,
description: master?.item?.description,
permissions: initialPermissions,
});
setDescriptionLength(master?.item?.description?.length || 0);
}, [master, reset]);
updateApplicationRole({ id: payload.id, payload });
};
// const onSubmit = (data) => {
// setIsLoading(true)
// const existingIds = new Set(
// master?.item?.featurePermission?.map((p) => p.id)
// );
const [descriptionLength, setDescriptionLength] = useState(master?.item?.description?.length || 0);
const maxDescriptionLength = 255;
useEffect(() => {
popoverRefs.current.forEach((el) => {
if (el) {
new bootstrap.Popover(el, {
trigger: "focus",
placement: "right",
html: true,
content: el.getAttribute("data-bs-content"), // use inline content from attribute
});
}
});
}, [masterFeatures]);
// const updatedPermissions = Object.entries(data.permissions)
// .filter(([id, value]) => {
// if (existingIds.has(id)) return true;
// return (
// value === true ||
// (dirtyFields.permissions && dirtyFields.permissions[id])
// );
// })
// .map(([id, value]) => ({ id, isEnabled: value }));
// 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 (
<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">
<label className="form-label">Role</label>
<input type="text"

View File

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

View File

@ -8,8 +8,8 @@ import EditJobRole from "./EditJobRole";
import CreateActivity from "./CreateActivity";
import EditActivity from "./EditActivity";
import ConfirmModal from "../common/ConfirmModal";
import {MasterRespository} from "../../repositories/MastersRepository";
import {cacheData, getCachedData} from "../../slices/apiDataManager";
import { MasterRespository } from "../../repositories/MastersRepository";
import { cacheData, getCachedData } from "../../slices/apiDataManager";
import showToast from "../../services/toastService";
import CreateWorkCategory from "./CreateWorkCategory";
import EditWorkCategory from "./EditWorkCategory";
@ -17,35 +17,55 @@ import CreateCategory from "./CreateContactCategory";
import CreateContactTag from "./CreateContactTag";
import EditContactCategory from "./EditContactCategory";
import EditContactTag from "./EditContactTag";
import { useDeleteMasterItem } from "../../hooks/masterHook/useMaster";
const MasterModal = ({ modaldata, closeModal }) => {
const [ isDeleteModalOpen, setIsDeleteModalOpen ] = useState( false );
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const { mutate: deleteMasterItem, isPending } = useDeleteMasterItem();
const handleSelectedMasterDeleted = async () =>
{
const deleteFn = MasterRespository[modaldata.masterType];
if (!deleteFn) {
showToast(`No delete strategy defined for master type`,"error");
return false;
// const handleSelectedMasterDeleted = async () =>
// {
// debugger
// const deleteFn = MasterRespository[modaldata.masterType];
// if (!deleteFn) {
// 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
{
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");
}
}
deleteMasterItem(
{
masterType: modaldata.masterType,
item: modaldata.item,
validateFn: modaldata.validateFn, // optional
},
{
onSuccess: () => {
handleCloseDeleteModal();
},
}
);
};
useEffect(() => {
if (modaldata?.modalType === "delete") {
@ -55,7 +75,7 @@ const MasterModal = ({ modaldata, closeModal }) => {
const handleCloseDeleteModal = () => {
setIsDeleteModalOpen(false);
closeModal();
closeModal();
};
if (modaldata?.modalType === "delete" && isDeleteModalOpen) {
@ -88,7 +108,9 @@ const MasterModal = ({ modaldata, closeModal }) => {
>
<div
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-md"
} modal-simple`}
@ -96,18 +118,21 @@ const MasterModal = ({ modaldata, closeModal }) => {
<div className="modal-content">
<div className="modal-body p-sm-4 p-0">
<div className="d-flex justify-content-between">
<h6>{ `${modaldata?.modalType} `}</h6>
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
onClick={closeModal}
></button>
</div>
<h6>{`${modaldata?.modalType} `}</h6>
<button
type="button"
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
onClick={closeModal}
></button>
</div>
{modaldata.modalType === "Application Role" && (
<CreateRole masmodalType={modaldata.masterType} onClose={closeModal} />
<CreateRole
masmodalType={modaldata.masterType}
onClose={closeModal}
/>
)}
{modaldata.modalType === "Edit-Application Role" && (
<EditRole master={modaldata} onClose={closeModal} />
@ -122,7 +147,10 @@ const MasterModal = ({ modaldata, closeModal }) => {
<CreateActivity onClose={closeModal} />
)}
{modaldata.modalType === "Edit-Activity" && (
<EditActivity activityData={modaldata.item} onClose={closeModal} />
<EditActivity
activityData={modaldata.item}
onClose={closeModal}
/>
)}
{modaldata.modalType === "Work Category" && (
<CreateWorkCategory onClose={closeModal} />
@ -133,10 +161,10 @@ const MasterModal = ({ modaldata, closeModal }) => {
{modaldata.modalType === "Contact Category" && (
<CreateCategory data={modaldata.item} onClose={closeModal} />
)}
{modaldata.modalType === "Edit-Contact Category" && (
{modaldata.modalType === "Edit-Contact Category" && (
<EditContactCategory data={modaldata.item} onClose={closeModal} />
)}
{modaldata.modalType === "Contact Tag" && (
{modaldata.modalType === "Contact Tag" && (
<CreateContactTag data={modaldata.item} onClose={closeModal} />
)}
{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 { cacheData,getCachedData } from "../../slices/apiDataManager";
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 = () =>
useMasterData("masterRole", MasterRespository.getRoles);
// const useMaster = () => {
const useMaster = (isMa) => {
const selectedMaster = useSelector((store)=>store.localVariables.selectedMaster);
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
const fetchData = async () => {
if (!selectedMaster) return;
setLoading(true);
try {
const cachedData = getCachedData(selectedMaster);
if (cachedData) {
// const selectedMaster = useSelector((store)=>store.localVariables.selectedMaster);
// const [data, setData] = useState([]);
// const [loading, setLoading] = useState(true);
// const [error, setError] = useState("");
// useEffect(() => {
// const fetchData = async () => {
// if (!selectedMaster) return;
// setLoading(true);
// try {
// const cachedData = getCachedData(selectedMaster);
// if (cachedData) {
setData(cachedData);
// setData(cachedData);
} else {
let response;
switch (selectedMaster) {
case "Application Role":
response = await MasterRespository.getRoles();
response = response.data;
break;
case "Job Role":
response = await MasterRespository.getJobRole();
response = response.data
break;
case "Activity":
response = await MasterRespository.getActivites();
response = response.data
break;
case "Work Category":
response = await MasterRespository.getWorkCategory();
response = response.data
break;
case "Contact Category":
response = await MasterRespository.getContactCategory();
response = response.data
break;
case "Contact Tag":
response = await MasterRespository.getContactTag();
response = response.data
break;
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"}];
break;
default:
response = [];
}
// } else {
// let response;
// switch (selectedMaster) {
// case "Application Role":
// response = await MasterRespository.getRoles();
// response = response.data;
// break;
// case "Job Role":
// response = await MasterRespository.getJobRole();
// response = response.data
// break;
// case "Activity":
// response = await MasterRespository.getActivites();
// response = response.data
// break;
// case "Work Category":
// response = await MasterRespository.getWorkCategory();
// response = response.data
// break;
// case "Contact Category":
// response = await MasterRespository.getContactCategory();
// response = response.data
// break;
// case "Contact Tag":
// response = await MasterRespository.getContactTag();
// response = response.data
// break;
// 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"}];
// break;
// default:
// response = [];
// }
if (response) {
setData(response);
cacheData(selectedMaster, response);
}
}
} catch (err) {
setError("Failed to fetch data.");
} finally {
setLoading(false);
}
};
// if (response) {
// setData(response);
// cacheData(selectedMaster, response);
// }
// }
// } catch (err) {
// setError("Failed to fetch data.");
// } finally {
// 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 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}
}
// ================================Mutation====================================
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 = () =>
// Job Role-----------------------------------
export const useUpdateJobRole = (onSuccessCallback, onErrorCallback) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ id, payload }) => {
const response = await MasterRespository.updateJobRole(id, payload);
return response.data;
},
onSuccess: (data, variables) => {
showToast("JobRole updated successfully.", "success");
queryClient.invalidateQueries({
queryKey: ["masterData", "Job Role"],
});
if (onSuccessCallback) onSuccessCallback(data);
},
onError: (error) => {
showToast(error.message || "Something went wrong", "error");
if (onErrorCallback) onErrorCallback(error);
},
});
};
export const useCreateJobRole = (onSuccessCallback) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (payload) => {
const response = await MasterRespository.createJobRole(payload);
return response.data;
},
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 [ loading, setLoading ] = useState( false )
const [ Error, setError ] = useState()
const fetchConatctCategory = async() =>
{
const cache_Category = getCachedData( "Contact Category" );
if ( !cache_Category )
const queryClient = useQueryClient();
return useMutation( {
mutationFn: async ( payload ) =>
{
try
{
let resp = await MasterRespository.getContactCategory();
setContactCategory( resp.data );
cacheData("Contact Category",resp.data)
} catch ( error )
{
setError(error)
}
} else
const resp = await MasterRespository.createRole( payload );
return resp.data;
},
onSuccess: ( data ) =>
{
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(() => {
const fetchContactTag = async () => {
const cache_Tags = getCachedData("Contact Tag");
export const useUpdateApplicationRole = (onSuccessCallback) =>
{
const queryClient = useQueryClient();
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);
return useMutation({
mutationFn: async ( {id, payload} ) =>
{
const response = await MasterRespository.updateRoles(id,payload);
return response.data;
},
onSuccess: (data, variables) => {
queryClient.invalidateQueries({
queryKey: ["masterData", "Application Role"],
});
showToast("Application Role updated successfully.", "success");
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 EmployeeRepository from "../repositories/EmployeeRepository";
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");
if (!EmployeeList_cached) {
setLoading(true);
const response = await EmployeeRepository.getAllEmployeeList(showInactive);
cacheData("AllEmployees", response.data);
setEmployeeList(response.data);
setLoading(false);
} else {
setEmployeeList(EmployeeList_cached);
setLoading(false);
}
} catch (error) {
setError("Failed to fetch data.");
setLoading(false);
}
// Query ---------------------------------------------------------------------------
export const useAllEmployees = ( showInactive ) =>
{
const {
data = [],
isLoading,
error,
refetch, // optional if you want recall functionality
} = useQuery({
queryKey: ['allEmployee', showInactive],
queryFn: async () => {
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) => {
const [employees, setEmployeeList] = useState([]);
const [loading, setLoading] = useState(true);
const [projects, setProjects] = useState([]);
// ManageBucket.jsx
export const useEmployees = ( selectedProject ) =>
{
const fetchData = async (projectid) => {
try {
let EmployeeByProject_Cache = getCachedData("employeeListByProject");
if (
!EmployeeByProject_Cache ||
!EmployeeByProject_Cache.projectId === projectid
) {
EmployeeRepository.getEmployeeListByproject(projectid)
.then((response) => {
setEmployeeList(response);
cacheData("employeeListByProject", {
data: response,
projectId: projectid,
});
})
.catch((error) => {
setError("Failed to fetch data.");
});
} else {
setEmployeeList(EmployeeByProject_Cache.data);
}
setLoading(false);
} catch (err) {
setError("Failed to fetch data.");
setLoading(false);
}
const {
data = [],
isLoading,
error,
refetch,
} = useQuery({
queryKey: ["employeeListByProject", selectedProject],
queryFn: async () => {
const res = await EmployeeRepository.getEmployeeListByproject(selectedProject);
return res.data || res;
},
enabled: !!selectedProject,
});
return {
employees: data,
loading: isLoading,
projects: [], // if needed, pass this separately or manage from another hook
reCallAllEmployee: refetch,
error,
};
useEffect(() => {
if (selectedProject) {
fetchData(selectedProject);
}
}, [selectedProject]);
return { employees, loading, projects, reCallAllEmployee };
};
// ManageRole.jsx
export const useEmployeeRoles = (employeeId) => {
const [loading, setLoading] = useState(true);
const [error, setError] = useState();
const [employeeRoles, setEmployeeRoles] = useState([]);
const fetchData = async (employeeid) => {
try {
let response = await RolesRepository.getEmployeeRoles(employeeid);
setEmployeeRoles(response.data);
cacheData("employeelist", response.data);
} catch (err) {
setError("Failed to fetch data.");
setEmployeeRoles([]);
} finally {
setLoading(false);
}
const {
data = [],
isLoading: loading,
error,
} = useQuery({
queryKey: ['employeeRoles', employeeId],
queryFn: async () => {
const res = await RolesRepository.getEmployeeRoles(employeeId);
return res.data;
},
enabled: !!employeeId,
});
return {
employeeRoles: data,
loading,
error,
};
useEffect(() => {
if (employeeId) {
fetchData(employeeId);
}
}, [employeeId]);
return { employeeRoles, loading, error };
};
// EmployeeProfile.jsx
export const useEmployeesByProject = (projectId) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const [employees, setEmployees] = useState([]);
const {
data = [],
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 () => {
const Employees_cache = getCachedData("employeeListByProject");
if (!Employees_cache || Employees_cache.projectId !== projectId) {
setEmployees(true);
ProjectRepository.getEmployeesByProject(projectId)
.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);
}
return {
employees: data,
loading,
error,
recallProjectEmplloyee,
};
useEffect(() => {
fetchData(projectId);
}, [projectId]);
return { employees, loading, error, recallProjectEmplloyee: fetchData };
};
// EmployeeList.jsx
export const useEmployeesAllOrByProjectId = (projectId, showInactive) => {
const [employees, setEmployees] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const isAllEmployees = !projectId && projectId !== undefined;
const fetchData = async (showInactive) => {
if ( 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";
const queryKey = isAllEmployees
? ['allEmployees', showInactive]
: ['projectEmployees', projectId];
try {
const response = await EmployeeRepository.getAllEmployeeList(
showInactive
);
setEmployees(response.data);
cacheData(cacheKey, { data: response.data });
setLoading(false);
} catch (err) {
setError("Failed to fetch data.");
setLoading(false);
}
const queryFn = async () => {
if (isAllEmployees) {
const res = await EmployeeRepository.getAllEmployeeList(showInactive);
return res.data;
} else {
const res = await ProjectRepository.getEmployeesByProject(projectId);
return res.data;
}
};
useEffect(() => {
fetchData(showInactive); // Fetch data when the component mounts or projectId changes
}, [projectId]); // Re-fetch when projectId changes
const {
data: employees = [],
isLoading,
error,
refetch,
} = useQuery({
queryKey,
queryFn,
enabled: isAllEmployees || !!projectId,
});
return {
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,
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 () => {
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);
}
};
// Mutation------------------------------------------------------------------
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 { cacheData,getCachedData } from "../slices/apiDataManager";
import { MasterRespository } from "../repositories/MastersRepository";
import { useQuery } from "@tanstack/react-query";
export const useMasterRole =()=>{
@ -43,40 +43,55 @@ export const useMasterRole =()=>{
return {masterRole,loading}
}
export const useFeatures =()=> {
const [masterFeatures, setMasterFeatures] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
// export const useFeatures =()=> {
// const [masterFeatures, setMasterFeatures] = useState([]);
// const [loading, setLoading] = useState(true);
// const [error, setError] = useState("");
const fetchData = async () => {
// const fetchData = async () => {
try {
const features_cache = getCachedData("masterFeatures");
if (!features_cache) {
MasterRespository.getFeatures()
.then((response) => {
setMasterFeatures(response.data);
// try {
// const features_cache = getCachedData("masterFeatures");
// if (!features_cache) {
// MasterRespository.getFeatures()
// .then((response) => {
// setMasterFeatures(response.data);
cacheData("features", response.data);
setLoading(false)
})
.catch((error) => {
setError("Failed to fetch data.");
});
}else{
if (!masterFeatures.length) setMasterFeatures(features_cache);
}
} catch (err) {
setError("Failed to fetch data.");
} finally {
setLoading(false);
}
};
// cacheData("features", response.data);
// setLoading(false)
// })
// .catch((error) => {
// setError("Failed to fetch data.");
// });
// }else{
// if (!masterFeatures.length) setMasterFeatures(features_cache);
// }
// } catch (err) {
// setError("Failed to fetch data.");
// } finally {
// setLoading(false);
// }
// };
useEffect(()=>{
fetchData();
},[])
// useEffect(()=>{
// 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);
};
return { currentPage, totalPages, currentItems, paginate };
return { currentPage, totalPages, currentItems, paginate,setCurrentPage };
};
export default usePagination;

View File

@ -3,60 +3,99 @@ import AuthRepository from "../repositories/AuthRepository";
import {cacheData, cacheProfileData, getCachedData, getCachedProfileData} from "../slices/apiDataManager";
import {useSelector} from "react-redux";
import eventBus from "../services/eventBus";
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
let hasFetched = false;
let hasReceived = false;
export const useProfile = () => {
const loggedUser = useSelector( ( store ) => store.globalVariables.loginUser );
const [profile, setProfile] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
// export const useProfile = () => {
// const loggedUser = useSelector( ( store ) => store.globalVariables.loginUser );
// const [profile, setProfile] = useState(null);
// const [loading, setLoading] = useState(false);
// const [error, setError] = useState("");
const fetchData = async () => {
try {
setLoading(true);
let response = await AuthRepository.profile();
setProfile(response.data);
cacheProfileData(response.data);
} catch (error) {
setError("Failed to fetch data.");
} finally {
setLoading(false);
}
};
// const fetchData = async () => {
// try {
// setLoading(true);
// let response = await AuthRepository.profile();
// setProfile(response.data);
// cacheProfileData(response.data);
// } catch (error) {
// setError("Failed to fetch data.");
// } finally {
// setLoading(false);
// }
// };
const validation = () => {
if (!hasFetched) {
hasFetched = true;
if (!loggedUser) {
fetchData();
} else {
setProfile(loggedUser);
}
}
// const validation = () => {
// if (!hasFetched) {
// hasFetched = true;
// if (!loggedUser) {
// fetchData();
// } else {
// setProfile(loggedUser);
// }
// }
setProfile(loggedUser);
}
// setProfile(loggedUser);
// }
useEffect(() => {
validation();
}, [loggedUser]);
// useEffect(() => {
// validation();
// }, [loggedUser]);
const handler = useCallback(
(data) => {
if(!getCachedData("hasReceived")){
cacheData("hasReceived", true);
hasFetched = false;
validation();
}
},[]
);
// const handler = useCallback(
// (data) => {
// if(!getCachedData("hasReceived")){
// cacheData("hasReceived", true);
// hasFetched = false;
// 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(() => {
eventBus.on("assign_project_one", handler);
return () => eventBus.off("assign_project_one", 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 EmployeeList from "../components/Directory/EmployeeList";
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 = () => {
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]);
const {
data: projects = [],
isLoading: loading,
error,
refetch,
} = useQuery({
queryKey: ['ProjectsList'],
queryFn: async () => {
const response = await ProjectRepository.getProjectList();
return response.data;
},
enabled: !!loggedUser,
});
return {
projectList,
projects,
loading,
error,
refetch: fetchProjects,
refetch,
};
};
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);
export const useEmployeesByProjectAllocated = (selectedProject) =>
{
const {data = [], isLoading, refetch, error} = useQuery( {
queryKey: ["empListByProjectAllocated", selectedProject ],
queryFn: async () =>
{
const res = await ProjectRepository.getProjectAllocation( selectedProject );
return res.data || res
},
enabled: !!selectedProject,
onError: ( error ) =>
{
showToast(error.message || "Error while Fetching project Allocated Employees", "error");
}
};
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 { cacheData, getCachedData } from "../slices/apiDataManager";
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) => {
const [TaskList, setTaskList] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const enabled = !!projectId && !!dateFrom && !!toDate;
const fetchList = async (projectId, dateFrom, toDate) => {
const taskList_cached = getCachedData("taskList");
// if (!taskList_cached || taskList_cached?.projectId !== projectId) {
try {
setLoading(true);
const resp = await TasksRepository.getTaskList(
projectId,
dateFrom,
toDate
);
setTaskList(resp.data);
cacheData("taskList", { projectId: projectId, data: resp.data });
setLoading(false);
} catch (err) {
setLoading(false);
setError(err);
}
// } else {
// setTaskList(taskList_cached.data);
// }
};
useEffect( () =>
{
const {
data: TaskList = [],
isLoading: loading,
error,
refetch,
} = useQuery({
queryKey: ["taskList", projectId, dateFrom, toDate],
queryFn: async () => {
const response = await TasksRepository.getTaskList(
projectId,
dateFrom,
toDate
);
return response.data;
},
enabled,
});
if (projectId && dateFrom && toDate) {
fetchList(projectId, dateFrom, toDate);
}
}, [projectId, dateFrom, toDate]);
return { TaskList, loading, error, refetch };
};
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 [loading, setLoading] = useState(false);
const [ error, setError ] = useState( null );
const fetchTask = async(TaskId) =>
{
try
const queryClient = useQueryClient();
const {
mutate,
isPending,
isSuccess,
isError,
error,
} = useMutation({
mutationFn: async ( {reportData,workAreaId} ) =>
{
let res = await TasksRepository.getTaskById( TaskId );
setTask( res.data );
} catch ( error )
debugger
return await TasksRepository.reportTask(reportData);
},
onSuccess: ( data, variables ) =>
{
setError(err)
}
}
useEffect( () =>
{
if ( TaskId )
{
fetchTask(TaskId)
}
}, [ TaskId ] )
return { Task,loading}
}
const {workAreaId} = variables;
queryClient.invalidateQueries( {queryKey: [ "taskList" ]} );
queryClient.invalidateQueries( {queryKey: [ "WorkItems", workAreaId ]} );
queryClient.invalidateQueries( {queryKey: [ 'ProjectsList' ]} );
showToast( "Task Reported Successfully.", "success" );
if (onSuccessCallback) onSuccessCallback(data);
},
onError: (error) => {
const msg =
error?.response?.data?.message || error.message || "Error occurred during API call";
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 [ error, setError ] = useState( '' );
const [ loading, setLoading ] = useState( false )
const fetchStatus = async() =>
{
try
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({payload,workAreaId}) => {
return await TasksRepository.assignTask(payload);
},
onSuccess: ( _, variables ) =>
{
const res = await MasterRespository.getAuditStatus()
setStatus( res.data )
cacheData("AuditStatus",res.data)
} catch ( err )
queryClient.invalidateQueries( {queryKey: [ "taskList" ]} );
queryClient.invalidateQueries( {queryKey: [ "WorkItems", variables?.workAreaId ]} );
showToast( "Task Assigned Successfully.", "success" );
if (onSuccessCallback) onSuccessCallback(variables);
},
onError: ( error ) =>
{
setError(err)
}
}
useEffect(() => {
const cache_status = getCachedData('AuditStatus');
if (cache_status) {
setStatus(cache_status);
} else {
fetchStatus();
}
}, []);
return {status,error,loading}
}
showToast("Something went wrong. Please try again.", "error");
if (onErrorCallback) onErrorCallback(error);
},
});
};

View File

@ -1,5 +1,17 @@
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 = () => {
return (

View File

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

View File

@ -1,110 +1,9 @@
import React, { useState, useEffect } from "react";
import "../../components/Project/ProjectInfra.css";
import ProjectRepository from "../../repositories/ProjectRepository";
import React from "react";
import Breadcrumb from "../../components/common/Breadcrumb";
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 { 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 (
<>
@ -115,17 +14,7 @@ const TaskPlannng = () => {
{ label: "Daily Task Planning" }
]}
></Breadcrumb>
{project_listLoader && <p>Loading..</p>}
{!project_listLoader && projects.length === 0 && (
<p>No Project Found.</p>
)}
{!project_listLoader && projects.length > 0 && (
<InfraPlanning
data={projectDetails}
activityMaster={activities}
onDataChange={handleDataChange}
/>
)}
<InfraPlanning/>
</div>
</>
);

View File

@ -5,10 +5,10 @@ import { Link, NavLink, useNavigate } from "react-router-dom";
import Avatar from "../../components/common/Avatar";
import Breadcrumb from "../../components/common/Breadcrumb";
import ManageEmp from "../../components/Employee/ManageRole";
import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees";
import { useProjects } from "../../hooks/useProjects"; // Keep if you use projects elsewhere
import { useProfile } from "../../hooks/useProfile"; // Keep if you use profile elsewhere
import { hasUserPermission } from "../../utils/authUtils"; // Keep if you use this elsewhere
import { useEmployeesAllOrByProjectId, useSuspendEmployee } from "../../hooks/useEmployees";
import { useProjects } from "../../hooks/useProjects";
import { useProfile } from "../../hooks/useProfile";
import { hasUserPermission } from "../../utils/authUtils";
import { ITEMS_PER_PAGE, MANAGE_EMPLOYEES } from "../../utils/constants";
import { clearCacheKey } from "../../slices/apiDataManager";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
@ -26,6 +26,7 @@ import { useSelector } from "react-redux";
import eventBus from "../../services/eventBus";
import { newlineChars } from "pdf-lib";
import GlobalModel from "../../components/common/GlobalModel";
import usePagination from "../../hooks/usePagination";
const EmployeeList = () => {
const selectedProjectId = useSelector(
@ -38,14 +39,15 @@ const EmployeeList = () => {
const { employees, loading, setLoading, error, recallEmployeeData } =
useEmployeesAllOrByProjectId(
showAllEmployees ? null : selectedProjectId, // Use selectedProjectId here
showAllEmployees ? null : selectedProjectId,
showInactive
);
const [employeeList, setEmployeeList] = useState([]);
const [modelConfig, setModelConfig] = useState();
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage] = useState(ITEMS_PER_PAGE);
const [ modelConfig, setModelConfig ] = useState();
const [EmpForManageRole,setEmpForManageRole] = useState(null)
// const [currentPage, setCurrentPage] = useState(1);
// const [itemsPerPage] = useState(ITEMS_PER_PAGE);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [searchText, setSearchText] = useState("");
const [filteredData, setFilteredData] = useState([]);
@ -53,135 +55,131 @@ const EmployeeList = () => {
const [selectedEmployeeId, setSelecedEmployeeId] = useState(null);
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
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();
/**
* 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) => {
if (!text) {
return data;
}
const lowercasedText = text.toLowerCase().trim(); // Ensure search text is trimmed and lowercase
if (!text) {
return data;
}
return data.filter((item) => {
// **IMPROVED FULL NAME CONSTRUCTION**
const firstName = item.firstName || "";
const middleName = item.middleName || "";
const lastName = item.lastName || "";
const lowercasedText = text.toLowerCase().trim();
// Join parts, then trim any excess spaces if a middle name is missing
const fullName = `${firstName} ${middleName} ${lastName}`.toLowerCase().trim().replace(/\s+/g, ' ');
return data.filter((item) => {
const firstName = item.firstName || "";
const middleName = item.middleName || "";
const lastName = item.lastName || "";
const email = item.email ? item.email.toLowerCase() : "";
const phoneNumber = item.phoneNumber ? item.phoneNumber.toLowerCase() : "";
const jobRole = item.jobRole ? item.jobRole.toLowerCase() : "";
const fullName = `${firstName} ${middleName} ${lastName}`
.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 value = e.target.value;
setSearchText(value);
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 results = applySearchFilter(sorted, searchText);
setFilteredData(results);
} else if (!loading && !employees) {
setEmployeeList([]);
setFilteredData([]);
}
}, [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 = () => {
const displayData = searchText ? filteredData : employeeList;
const { currentPage, totalPages, currentItems, paginate,setCurrentPage } = usePagination(
displayData,
ITEMS_PER_PAGE
);
const openModal = () => {
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) => {
setModelConfig(config);
};
useEffect(() => {
if (modelConfig !== null) {
openModal();
}
}, [modelConfig, isCreateModalOpen]);
// useEffect(() => {
// if (modelConfig !== null) {
// openModal();
// }
// }, [modelConfig, isCreateModalOpen]);
const tableRef = useRef(null);
const handleExport = (type) => {
@ -215,9 +213,6 @@ const handleAllEmployeesToggle = (e) => {
setShowInactive(false);
setShowAllEmployees(isChecked);
if (!isChecked) {
setSelectedProject(selectedProjectId || "");
}
};
const handleEmployeeModel = (id) => {
@ -253,34 +248,18 @@ const handleAllEmployeesToggle = (e) => {
return (
<>
{isCreateModalOpen && (
<ManageEmp employeeId={modelConfig} onClosed={closeModal} />
{EmpForManageRole && (
<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 && (
<GlobalModel isOpen={showModal} size="lg" closeModal={()=>setShowModal(false)}>
<ManageEmployee
employeeId={selectedEmployeeId}
onClosed={()=>setShowModal(false)}
onClosed={() => setShowModal( false )}
IsAllEmployee={showAllEmployees}
/>
</GlobalModel>
)}
@ -594,91 +573,90 @@ const handleAllEmployeesToggle = (e) => {
</span>
</td>
<td className=" d-none d-md-table-cell">
{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 className=" d-none d-md-table-cell">
{moment(item.joiningDate)?.format("DD-MMM-YYYY")}
</td>
)}
</tr>
))}
<td>
{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>
</table>
<div style={{ width: "1%" }}></div>
{/* Pagination */}
{!loading && displayData.length > itemsPerPage && (
{!loading && displayData.length > ITEMS_PER_PAGE && (
<nav aria-label="Page">
<ul className="pagination pagination-sm justify-content-end py-1">
<li

View File

@ -20,6 +20,7 @@ import Avatar from "../../components/common/Avatar";
import AttendancesEmployeeRecords from "./AttendancesEmployeeRecords";
import ManageEmployee from "../../components/Employee/ManageEmployee";
import { useChangePassword } from "../../components/Context/ChangePasswordContext";
import GlobalModel from "../../components/common/GlobalModel";
const EmployeeProfile = () => {
const { profile } = useProfile();
@ -39,11 +40,7 @@ const EmployeeProfile = () => {
setActivePill(pillKey);
};
const closeModal = () => {
setShowModal(false);
fetchEmployeeProfile(employeeId);
};
const handleShow = () => setShowModal(true);
const fetchEmployeeProfile = async (employeeID) => {
try {
@ -104,24 +101,10 @@ const EmployeeProfile = () => {
const { openChangePassword } = useChangePassword();
return (
<>
{" "}
{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={employeeId} onClosed={closeModal} />
</div>
</div>
</div>
<GlobalModel size="lg" isOpen={showModal} closeModal={()=>setShowModal(false)}>
<ManageEmployee employeeId={employeeId} onClosed={()=>setShowModal(false)} />
</GlobalModel>
)}
<div className="container-fluid">
<Breadcrumb
@ -251,7 +234,7 @@ const EmployeeProfile = () => {
</div>
<button
className="btn btn-primary btn-block"
onClick={() => handleShow()}
onClick={()=>setShowModal(true)}
>
Edit Profile
</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 MasterModal from "../../components/master/MasterModal";
import { mastersList} from "../../data/masters";
@ -9,92 +9,80 @@ import MasterTable from "./MasterTable";
import { getCachedData } from "../../slices/apiDataManager";
import {useHasUserPermission} from "../../hooks/useHasUserPermission";
import { MANAGE_MASTER } from "../../utils/constants";
import {useQueryClient} from "@tanstack/react-query";
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 [searchTerm, setSearchTerm] = useState('');
const [ filteredResults, setFilteredResults ] = useState( [] );
const hasMasterPermission = useHasUserPermission( MANAGE_MASTER )
const dispatch = useDispatch();
const selectedMaster = useSelector((store)=>store.localVariables.selectedMaster)
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const openModal = () => {
setIsCreateModalOpen(true);
const hasMasterPermission = useHasUserPermission(MANAGE_MASTER);
const dispatch = useDispatch();
const selectedMaster = useSelector((store) => store.localVariables.selectedMaster);
const queryClient = useQueryClient();
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 (
<>

View File

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

View File

@ -3,10 +3,13 @@ import ProjectCard from "../../components/Project/ProjectCard";
import ManageProjectInfo from "../../components/Project/ManageProjectInfo";
import Breadcrumb from "../../components/common/Breadcrumb";
import ProjectRepository from "../../repositories/ProjectRepository";
import { useProjects } from "../../hooks/useProjects";
import { useDispatch } from "react-redux";
import { useProjects, useCreateProject } from "../../hooks/useProjects";
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 { useProfile } from "../../hooks/useProfile";
import { ITEMS_PER_PAGE, MANAGE_PROJECT } from "../../utils/constants";
@ -14,26 +17,33 @@ import ProjectListView from "./ProjectListView";
import eventBus from "../../services/eventBus";
import { clearApiCacheKey } from "../../slices/apiCacheSlice";
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 { profile: loginUser } = useProfile();
const [listView, setListView] = useState(false);
const [showModal, setShowModal] = useState(false);
const { projects, loading, error, refetch } = useProjects();
const [projectList, setProjectList] = useState([]);
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
const [HasManageProject, setHasManageProject] = useState(
HasManageProjectPermission
);
const dispatch = useDispatch();
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage] = useState(ITEMS_PER_PAGE);
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
const [HasManageProject, setHasManageProject] = useState(HasManageProjectPermission);
const { mutate: createProject,isPending } = useCreateProject({
onSuccessCallback: () => {
setShowModal(false);
},
});
const [searchTerm, setSearchTerm] = useState("");
const [selectedStatuses, setSelectedStatuses] = useState([
"b74da4c2-d07e-46f2-9919-e75e49b12731",
"603e994b-a27f-4e5d-a251-f3d69b0498ba",
"ef1c356e-0fe0-42df-a5d3-8daee355492d",
"cdad86aa-8a56-4ff4-b633-9c629057dfef",
"33deaef9-9af1-4f2a-b443-681ea0d04f81",
]);
@ -53,48 +63,29 @@ const ProjectList = () => {
.filter((statusId) => grouped[statusId])
.flatMap((statusId) =>
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(() => {
sortingProject(projects);
}, [projects, loginUser?.projects, loading]);
if (!loading && projects) {
sortingProject(projects);
}
}, [projects, loading, selectedStatuses]);
useEffect(() => {
if (loginUser) {
setHasManageProject(HasManageProjectPermission);
} else {
setHasManageProject(false);
}
setHasManageProject(loginUser ? HasManageProjectPermission : false);
}, [loginUser, HasManageProjectPermission]);
const handleSubmitForm = (newProject, setloading, reset) => {
ProjectRepository.manageProject(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 handleSubmitForm = (newProject) => {
createProject(newProject);
};
const handleStatusChange = (statusId) => {
@ -118,13 +109,14 @@ const ProjectList = () => {
return matchesStatus && matchesSearch;
});
const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const currentItems = filteredProjects.slice(
indexOfFirstItem,
indexOfLastItem
);
const totalPages = Math.ceil(filteredProjects.length / itemsPerPage);
const totalPages = Math.ceil(filteredProjects.length / ITEMS_PER_PAGE);
const {
currentItems,
currentPage,
paginate,
setCurrentPage,
} = usePagination(filteredProjects, ITEMS_PER_PAGE);
useEffect(() => {
const tooltipTriggerList = Array.from(
@ -133,67 +125,19 @@ const ProjectList = () => {
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 (
<>
<div
className={`modal fade ${showModal ? "show" : ""}`}
tabIndex="-1"
role="dialog"
style={{ display: showModal ? "block" : "none" }}
aria-hidden={!showModal}
>
<ManageProjectInfo
{showModal && (
<GlobalModel isOpen={showModal} closeModal={handleClose}>
<ManageProjectInfo
project={null}
handleSubmitForm={handleSubmitForm}
onClose={handleClose}
isPending={isPending}
/>
</div>
</GlobalModel>
)}
<div className="container-fluid">
<Breadcrumb
@ -262,6 +206,10 @@ const ProjectList = () => {
id: "b74da4c2-d07e-46f2-9919-e75e49b12731",
label: "Active",
},
{
id: "cdad86aa-8a56-4ff4-b633-9c629057dfef",
label:"In Progress"
},
{
id: "603e994b-a27f-4e5d-a251-f3d69b0498ba",
label: "On Hold",
@ -318,11 +266,11 @@ const ProjectList = () => {
{listView ? (
<div className="card cursor-pointer">
<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">
<thead>
<tr>
<th className="text-start" colSpan={5}>
<th className="text-start" colSpan={5} >
Project Name
</th>
<th className="mx-2 text-start">Contact Person</th>
@ -345,6 +293,10 @@ const ProjectList = () => {
id: "b74da4c2-d07e-46f2-9919-e75e49b12731",
label: "Active",
},
{
id: "cdad86aa-8a56-4ff4-b633-9c629057dfef",
label:"In Progress"
},
{
id: "603e994b-a27f-4e5d-a251-f3d69b0498ba",
label: "On Hold",
@ -386,8 +338,8 @@ const ProjectList = () => {
</thead>
<tbody className="table-border-bottom-0 overflow-auto ">
{currentItems.length === 0 ? (
<tr>
<td colSpan="12" className="text-center py-4">
<tr className="text-center">
<td colSpan="12" rowSpan='12'style={{height:"200px"}} >
No projects found
</td>
</tr>

View File

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

View File

@ -41,7 +41,7 @@ export const MasterRespository = {
"Activity": ( id ) => api.delete( `/api/master/activity/delete/${ id }` ),
"Application Role":(id)=>api.delete(`/api/roles/${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}`),
getWorkCategory:() => api.get(`/api/master/work-categories`),

View File

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

View File

@ -8,7 +8,8 @@ import showToast from "./toastService";
import eventBus from "./eventBus";
import { useSelector } from "react-redux";
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;
let connection = null;
@ -57,7 +58,8 @@ export function startSignalR(loggedUser) {
data.keyword == "Create_Project" ||
data.keyword == "Update_Project"
) {
clearCacheKey("projectslist");
// clearCacheKey("projectslist");
queryClient.invalidateQueries(['projectslist']);
eventBus.emit("project", data);
}
@ -77,20 +79,37 @@ export function startSignalR(loggedUser) {
}
// if created or updated infra
if (data.keyword == "Infra") {
clearCacheKey("projectInfo");
eventBus.emit("infra", data);
queryClient.removeQueries({queryKey:["ProjectInfra"]})
// 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 (data.keyword == "Employee") {
clearCacheKey("employeeListByProject");
clearCacheKey("allEmployeeList");
clearCacheKey("allInactiveEmployeeList");
clearCacheKey("employeeProfile");
// clearCacheKey("employeeListByProject");
// clearCacheKey("allEmployeeList");
// clearCacheKey("allInactiveEmployeeList");
// clearCacheKey("employeeProfile");
clearCacheKey("Attendance");
clearCacheKey("regularizedList")
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") {

View File

@ -62,6 +62,11 @@ export const checkIfCurrentDate = (dateString) => {
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) =>{
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":
return "bg-label-secondary";
case "cdad86aa-8a56-4ff4-b633-9c629057dfef":
return "bg-label-success";
return "bg-label-primary";
}
};