diff --git a/public/assets/img/orglogo.png b/public/assets/img/orglogo.png new file mode 100644 index 00000000..51ce9299 Binary files /dev/null and b/public/assets/img/orglogo.png differ diff --git a/src/components/Documents/Documents.jsx b/src/components/Documents/Documents.jsx index 1b7f25f4..8210bf58 100644 --- a/src/components/Documents/Documents.jsx +++ b/src/components/Documents/Documents.jsx @@ -118,7 +118,7 @@ const Documents = ({ Document_Entity, Entity }) => { return (
-
+
{/* Search */}
diff --git a/src/components/Employee/DemoTable.jsx b/src/components/Employee/DemoTable.jsx deleted file mode 100644 index 1d645cc3..00000000 --- a/src/components/Employee/DemoTable.jsx +++ /dev/null @@ -1,172 +0,0 @@ -import React from "react"; - -const DemoTable = () => { - return ( -
-
-
-
- - - - - - - - - - - - - - -
idNameEmailDateSalaryStatusAction
-
-
-
-
-
- New Record -
- -
-
-
-
- -
- - - - -
-
-
- -
- - - - -
-
-
- -
- - - - -
-
- You can use letters, numbers & periods -
-
-
- -
- - - - -
-
-
- -
- - - - -
-
-
- - -
-
-
-
- -
- -
- -
-
- -
-
- ); -}; - -export default DemoTable; diff --git a/src/components/Employee/EmployeeList.jsx b/src/components/Employee/EmployeeList.jsx deleted file mode 100644 index 5a79c6ce..00000000 --- a/src/components/Employee/EmployeeList.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -const EmployeeList = () => { - return
EmployeeList
; -}; - -export default EmployeeList; \ No newline at end of file diff --git a/src/components/Employee/EmployeeSchema.jsx b/src/components/Employee/EmployeeSchema.jsx new file mode 100644 index 00000000..ba540ef4 --- /dev/null +++ b/src/components/Employee/EmployeeSchema.jsx @@ -0,0 +1,124 @@ +import { z } from "zod" + + +const mobileNumberRegex = /^[0-9]\d{9}$/; + +export const employeeSchema = + z.object({ + firstName: z.string().min(1, { message: "First Name is required" }), + middleName: z.string().optional(), + lastName: z.string().min(1, { message: "Last Name is required" }), + email: z + .string() + .max(80, "Email cannot exceed 80 characters") + .optional() + .refine((val) => !val || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val), { + message: "Invalid email format", + }) + .refine( + (val) => { + if (!val) return true; + const [local, domain] = val.split("@"); + return ( + val.length <= 320 && local?.length <= 64 && domain?.length <= 255 + ); + }, + { + message: "Email local or domain part is too long", + } + ), + currentAddress: z + .string() + .min(1, { message: "Current Address is required" }) + .max(500, { message: "Address cannot exceed 500 characters" }), + birthDate: z + .string() + .min(1, { message: "Birth Date is required" }) + .refine( + (date, ctx) => { + return new Date(date) <= new Date(); + }, + { + message: "Birth date cannot be in the future", + } + ), + joiningDate: z + .string() + .min(1, { message: "Joining Date is required" }) + .refine( + (date, ctx) => { + return new Date(date) <= new Date(); + }, + { + message: "Joining date cannot be in the future", + } + ), + emergencyPhoneNumber: z + .string() + .min(1, { message: "Phone Number is required" }) + .regex(mobileNumberRegex, { message: "Invalid phone number " }), + emergencyContactPerson: z + .string() + .min(1, { message: "Emergency Contact Person is required" }) + .regex(/^[A-Za-z\s]+$/, { + message: "Emergency Contact Person must contain only letters", + }), + aadharNumber: z + .string() + .optional() + .refine((val) => !val || /^\d{12}$/.test(val), { + message: "Aadhar card must be exactly 12 digits long", + }), + gender: z + .string() + .min(1, { message: "Gender is required" }) + .refine((val) => val !== "Select Gender", { + message: "Please select a gender", + }), + panNumber: z + .string() + .optional() + .refine((val) => !val || /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/.test(val), { + message: "Invalid PAN number", + }), + permanentAddress: z + .string() + .min(1, { message: "Permanent Address is required" }) + .max(500, { message: "Address cannot exceed 500 characters" }), + phoneNumber: z + .string() + .min(1, { message: "Phone Number is required" }) + .regex(mobileNumberRegex, { message: "Invalid phone number " }), + jobRoleId: z.string().min(1, { message: "Role is required" }), + organizationId:z.string().min(1,{message:"Organization is required"}), + hasApplicationAccess:z.boolean().default(false), + }).refine((data) => { + if (data.hasApplicationAccess) { + return data.email && data.email.trim() !== ""; + } + return true; +}, { + message: "Email is required when employee has access", + path: ["email"], +}); + + +export const defatEmployeeObj = { + firstName: "", + middleName: "", + lastName: "", + email: "", + currentAddress: "", + birthDate: "", + joiningDate: "", + emergencyPhoneNumber: "", + emergencyContactPerson: "", + aadharNumber: "", + gender: "", + panNumber: "", + permanentAddress: "", + phoneNumber: "", + jobRoleId: null, + organizationId:"", + hasApplicationAccess:false + } \ No newline at end of file diff --git a/src/components/Employee/ManageEmployee.jsx b/src/components/Employee/ManageEmployee.jsx index 81a4c4d2..f57ca994 100644 --- a/src/components/Employee/ManageEmployee.jsx +++ b/src/components/Employee/ManageEmployee.jsx @@ -1,36 +1,37 @@ import React, { useEffect, useState } from "react"; -import showToast from "../../services/toastService"; -import EmployeeRepository from "../../repositories/EmployeeRepository"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; + import useMaster from "../../hooks/masterHook/useMaster"; 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, useUpdateEmployee } from "../../hooks/useEmployees"; import { - cacheData, - clearCacheKey, - getCachedData, -} from "../../slices/apiDataManager"; -import { clearApiCacheKey } from "../../slices/apiCacheSlice"; -import { useMutation } from "@tanstack/react-query"; + useEmployeeProfile, + useUpdateEmployee, +} from "../../hooks/useEmployees"; + import Label from "../common/Label"; import DatePicker from "../common/DatePicker"; - -const mobileNumberRegex = /^[0-9]\d{9}$/; +import { defatEmployeeObj, employeeSchema } from "./EmployeeSchema"; +import { useOrganizationsList } from "../../hooks/useOrganization"; +import { ITEMS_PER_PAGE } from "../../utils/constants"; const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { const dispatch = useDispatch(); const { mutate: updateEmployee, isPending } = useUpdateEmployee(); - + const { + data: organzationList, + isLoading, + isError, + error: EempError, + } = useOrganizationsList(ITEMS_PER_PAGE, 1, true); const { employee, error, loading: empLoading, - refetch + refetch, } = useEmployeeProfile(employeeId); useEffect(() => { @@ -38,6 +39,7 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { }, [employeeId]); const [disabledEmail, setDisabledEmail] = useState(false); + const { data: job_role, loading } = useMaster(); const [isloading, setLoading] = useState(false); const navigation = useNavigate(); @@ -45,98 +47,9 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { const [currentAddressLength, setCurrentAddressLength] = useState(0); const [permanentAddressLength, setPermanentAddressLength] = useState(0); - const userSchema = z.object({ - ...(employeeId ? { id: z.string().optional() } : {}), - firstName: z.string().min(1, { message: "First Name is required" }), - middleName: z.string().optional(), - lastName: z.string().min(1, { message: "Last Name is required" }), - email: z - .string() - .max(80, "Email cannot exceed 80 characters") - .optional() - .refine((val) => !val || /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val), { - message: "Invalid email format", - }) - .refine( - (val) => { - if (!val) return true; - const [local, domain] = val.split("@"); - return ( - val.length <= 320 && local?.length <= 64 && domain?.length <= 255 - ); - }, - { - message: "Email local or domain part is too long", - } - ), - currentAddress: z - .string() - .min(1, { message: "Current Address is required" }) - .max(500, { message: "Address cannot exceed 500 characters" }), - birthDate: z - .string() - .min(1, { message: "Birth Date is required" }) - .refine( - (date, ctx) => { - return new Date(date) <= new Date(); - }, - { - message: "Birth date cannot be in the future", - } - ), - joiningDate: z - .string() - .min(1, { message: "Joining Date is required" }) - .refine( - (date, ctx) => { - return new Date(date) <= new Date(); - }, - { - message: "Joining date cannot be in the future", - } - ), - emergencyPhoneNumber: z - .string() - .min(1, { message: "Phone Number is required" }) - .regex(mobileNumberRegex, { message: "Invalid phone number " }), - emergencyContactPerson: z - .string() - .min(1, { message: "Emergency Contact Person is required" }) - .regex(/^[A-Za-z\s]+$/, { - message: "Emergency Contact Person must contain only letters", - }), - aadharNumber: z - .string() - .optional() - .refine((val) => !val || /^\d{12}$/.test(val), { - message: "Aadhar card must be exactly 12 digits long", - }), - gender: z - .string() - .min(1, { message: "Gender is required" }) - .refine((val) => val !== "Select Gender", { - message: "Please select a gender", - }), - panNumber: z - .string() - .optional() - .refine((val) => !val || /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/.test(val), { - message: "Invalid PAN number", - }), - permanentAddress: z - .string() - .min(1, { message: "Permanent Address is required" }) - .max(500, { message: "Address cannot exceed 500 characters" }), - phoneNumber: z - .string() - .min(1, { message: "Phone Number is required" }) - .regex(mobileNumberRegex, { message: "Invalid phone number " }), - jobRoleId: z.string().min(1, { message: "Role is required" }), - }); - useEffect(() => { - refetch() - }, []) + refetch(); + }, []); const { register, @@ -147,25 +60,8 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { reset, getValues, } = useForm({ - resolver: zodResolver(userSchema), - defaultValues: { - id: currentEmployee?.id || null, - firstName: currentEmployee?.firstName || "", - middleName: currentEmployee?.middleName || "", - lastName: currentEmployee?.lastName || "", - email: currentEmployee?.email || "", - currentAddress: currentEmployee?.currentAddress || "", - birthDate: formatDate(currentEmployee?.birthDate) || "", - joiningDate: formatDate(currentEmployee?.joiningDate) || "", - emergencyPhoneNumber: currentEmployee?.emergencyPhoneNumber || "", - emergencyContactPerson: currentEmployee?.emergencyContactPerson || "", - aadharNumber: currentEmployee?.aadharNumber || "", - gender: currentEmployee?.gender || "", - panNumber: currentEmployee?.panNumber || "", - permanentAddress: currentEmployee?.permanentAddress || "", - phoneNumber: currentEmployee?.phoneNumber || "", - jobRoleId: currentEmployee?.jobRoleId.toString() || null, - }, + resolver: zodResolver(employeeSchema), + defaultValues: defatEmployeeObj, mode: "onChange", }); @@ -176,7 +72,13 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { data.email = null; } - updateEmployee({ ...data, IsAllEmployee }, { + const payload = { ...data, IsAllEmployee }; + + if (employeeId) { + payload.id = employeeId; + } + + updateEmployee(payload, { onSuccess: () => { reset(); onClosed(); @@ -184,7 +86,6 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { }); }; - useEffect(() => { if (!loading && !error && employee) { setCurrentEmployee(employee); @@ -195,37 +96,47 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { reset( currentEmployee ? { - id: currentEmployee.id || null, - firstName: currentEmployee.firstName || "", - middleName: currentEmployee.middleName || "", - lastName: currentEmployee.lastName || "", - email: currentEmployee.email || "", - currentAddress: currentEmployee.currentAddress || "", - birthDate: formatDate(currentEmployee.birthDate) || "", - joiningDate: formatDate(currentEmployee.joiningDate) || "", - emergencyPhoneNumber: currentEmployee.emergencyPhoneNumber || "", - emergencyContactPerson: - currentEmployee.emergencyContactPerson || "", - aadharNumber: currentEmployee.aadharNumber || "", - gender: currentEmployee.gender || "", - panNumber: currentEmployee.panNumber || "", - permanentAddress: currentEmployee.permanentAddress || "", - phoneNumber: currentEmployee.phoneNumber || "", - jobRoleId: currentEmployee.jobRoleId?.toString() || "", - } + id: currentEmployee.id || null, + firstName: currentEmployee.firstName || "", + middleName: currentEmployee.middleName || "", + lastName: currentEmployee.lastName || "", + email: currentEmployee.email || "", + currentAddress: currentEmployee.currentAddress || "", + birthDate: formatDate(currentEmployee.birthDate) || "", + joiningDate: formatDate(currentEmployee.joiningDate) || "", + emergencyPhoneNumber: currentEmployee.emergencyPhoneNumber || "", + emergencyContactPerson: + currentEmployee.emergencyContactPerson || "", + aadharNumber: currentEmployee.aadharNumber || "", + gender: currentEmployee.gender || "", + panNumber: currentEmployee.panNumber || "", + permanentAddress: currentEmployee.permanentAddress || "", + phoneNumber: currentEmployee.phoneNumber || "", + jobRoleId: currentEmployee.jobRoleId?.toString() || "", + organizationId: currentEmployee.organizationId || "", + hasApplicationAccess: currentEmployee.hasApplicationAccess || false, + } : {} ); setCurrentAddressLength(currentEmployee?.currentAddress?.length || 0); setPermanentAddressLength(currentEmployee?.permanentAddress?.length || 0); }, [currentEmployee, reset]); + const hasAccessAplication = watch("hasApplicationAccess"); return ( <>
-

{employee ? "Update Employee" : "Create Employee"}

+
+

+ {" "} + {employee ? "Update Employee" : "Create Employee"} +

{" "} +
- + { }} /> {errors.firstName && ( -
+
{errors.firstName.message}
)} @@ -267,14 +181,18 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => { }} /> {errors.middleName && ( -
+
{errors.middleName.message}
)}
-
- + { }} /> {errors.lastName && ( -
+
{errors.lastName.message}
)}
-
-
Email
+ { )}
- + {
- +
- - {" "} - {500 - currentAddressLength} characters left - + {500 - currentAddressLength} characters left
{errors.currentAddress && (
{ }} >
- - {500 - permanentAddressLength} characters left - + {500 - permanentAddressLength} characters left
{errors.permanentAddress && (
{ )}
+ + {/* -------------- */} +
+
+ +
+ +
+ {errors.organizationId && ( +
+ {errors.organizationId.message} +
+ )} +
+ +
+ +
+
+ + {/* --------------- */}
{" "}
@@ -488,7 +469,9 @@ const ManageEmployee = ({ employeeId, onClosed, IsAllEmployee }) => {
- +
-
-
- )} -
- -
- ); }; diff --git a/src/components/Organization/AssignOrg.jsx b/src/components/Organization/AssignOrg.jsx index e30a417c..d0886a62 100644 --- a/src/components/Organization/AssignOrg.jsx +++ b/src/components/Organization/AssignOrg.jsx @@ -93,8 +93,15 @@ const AssignOrg = ({ setStep }) => {
{/* Organization Info Display */}
-
-
{orgData.name}
+
+
+ logo

{orgData.name}

+
- +
Organization Info
{/* Contact Info */}
@@ -114,7 +121,7 @@ const AssignOrg = ({ setStep }) => { className="form-label me-2 mb-0 fw-semibold" style={{ minWidth: "130px" }} > - Contact Person : + Contact Person :
{orgData.contactPerson}
@@ -125,7 +132,7 @@ const AssignOrg = ({ setStep }) => { className="form-label me-2 mb-0 fw-semibold" style={{ minWidth: "130px" }} > - Contact Number : + Contact Number :
{orgData.contactNumber}
@@ -136,7 +143,7 @@ const AssignOrg = ({ setStep }) => { className="form-label me-2 mb-0 fw-semibold" style={{ minWidth: "130px" }} > - Email Address : + Email Address :
{orgData.email}
@@ -147,7 +154,8 @@ const AssignOrg = ({ setStep }) => { className="form-label me-2 mb-0 fw-semibold" style={{ maxWidth: "130px" }} > - Service provider Id (SPRID) : + + Service Provider Id (SPRID) :
{orgData.sprid}
@@ -158,7 +166,7 @@ const AssignOrg = ({ setStep }) => { className="form-label me-1 mb-0 fw-semibold" style={{ minWidth: "130px" }} > - Address : + Address :
{orgData.address}
@@ -233,11 +241,11 @@ const AssignOrg = ({ setStep }) => {
diff --git a/src/components/Organization/ManagOrg.jsx b/src/components/Organization/ManagOrg.jsx index 0f4aa49e..cd27ff9b 100644 --- a/src/components/Organization/ManagOrg.jsx +++ b/src/components/Organization/ManagOrg.jsx @@ -2,6 +2,7 @@ import React, { useEffect } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { useCreateOrganization, + useOrganization, useOrganizationModal, useUpdateOrganization, } from "../../hooks/useOrganization"; @@ -18,6 +19,7 @@ const ManagOrg = () => { const { data: service, isLoading } = useGlobalServices(); const { flowType, orgData, startStep, onOpen, onClose, prevStep } = useOrganizationModal(); + const {data:organization,isLoading:organizationLoading,isError,error} = useOrganization(orgData?.id); const method = useForm({ resolver: zodResolver(organizationSchema), @@ -45,7 +47,7 @@ const ManagOrg = () => { onOpen({ startStep: 1 }); onClose(); }); - +console.log(organization) // Prefill form if editing useEffect(() => { if (orgData) { diff --git a/src/components/Organization/OrgPickerFromSPId.jsx b/src/components/Organization/OrgPickerFromSPId.jsx index 0a46778c..69774947 100644 --- a/src/components/Organization/OrgPickerFromSPId.jsx +++ b/src/components/Organization/OrgPickerFromSPId.jsx @@ -78,7 +78,7 @@ const OrgPickerFromSPId = ({ title, placeholder }) => {
logo { const { isOpen, orgData, startStep, onOpen, onClose, onToggle } = @@ -53,7 +54,7 @@ const OrganizationModal = () => { }; const RenderTitle = useMemo(() => { - if (orgData) { + if (orgData && startStep === 3 ) { return "Assign Organization"; } @@ -70,8 +71,11 @@ const OrganizationModal = () => { if (startStep === 3) { return "Assign Organization"; } + if(startStep === 5){ + return "Organization Details" + } - return "Manage Organization"; + return `${orgData ? "Update":"Create"} Organization`; }, [startStep, orgData]); const contentBody = ( @@ -94,6 +98,9 @@ const OrganizationModal = () => { {/* ---------- STEP 3: Add New Organization ---------- */} {startStep === 4 && } + + {/* ---------- STEP 3: View Organization ---------- */} + {startStep === 5 && }
); diff --git a/src/components/Organization/OrganizationSkeleton.jsx b/src/components/Organization/OrganizationSkeleton.jsx index bdb5feff..841815dc 100644 --- a/src/components/Organization/OrganizationSkeleton.jsx +++ b/src/components/Organization/OrganizationSkeleton.jsx @@ -42,3 +42,82 @@ export const OrgCardSkeleton = () => {
); }; + + +export const OrgDetailsSkeleton = () => { + return ( +
+ {/* Header */} +
+
+ {/* Logo + Name */} +
+ + +
+ + {/* Status Badge */} + +
+
+ + {/* Section Title */} +
+ +
+ + {/* Contact Person */} +
+
+ + +
+
+ + {/* Contact Number */} +
+
+ + +
+
+ + {/* Email */} +
+
+ + +
+
+ + {/* SPRID */} +
+
+ + +
+
+ + {/* Employees */} +
+
+ + +
+
+ + {/* Address */} +
+
+ + +
+
+ + {/* Section Title 2 */} +
+ +
+
+ ); +}; diff --git a/src/components/Organization/OrganizationsList.jsx b/src/components/Organization/OrganizationsList.jsx index b7ea28e5..6e170a2f 100644 --- a/src/components/Organization/OrganizationsList.jsx +++ b/src/components/Organization/OrganizationsList.jsx @@ -129,7 +129,7 @@ const OrganizationsList = ({searchText}) => { ))}
- + onOpen({startStep:5,orgData:org.id,flowType:"view"})}> onOpen({startStep:4,orgData:org,flowType:"edit"})}>
diff --git a/src/components/Organization/ViewOrganization.jsx b/src/components/Organization/ViewOrganization.jsx new file mode 100644 index 00000000..74c898b1 --- /dev/null +++ b/src/components/Organization/ViewOrganization.jsx @@ -0,0 +1,103 @@ +import React from "react"; +import { useOrganization } from "../../hooks/useOrganization"; +import { OrgDetailsSkeleton } from "./OrganizationSkeleton"; + +const VieworgDataanization = ({ orgId }) => { + const { data, isLoading, isError, error } = useOrganization(orgId); + if (isLoading) return ; + if (isError) return
{error.message}
; + return ( +
+ {/* Header */} +
+
+
+ logo

{data?.data?.name}

+
+
+ {data?.data.isActive ? "Active":"In-Active"} +
+
+
+
Organization Info
+ {/* Contact Info */} +
+
+ +
{data?.data?.contactPerson}
+
+
+
+
+ +
{data?.data?.contactNumber}
+
+
+
+
+ +
{data?.data?.email}
+
+
+
+
+ +
{data?.data?.sprid}
+
+
+ +
+
+ +
{data?.data?.activeEmployeeCount}
+
+
+
+
+ +
{data?.data?.address}
+
+
+
Projects And Services
+
+ ) +}; + +export default VieworgDataanization; diff --git a/src/components/Project/ManageProjectInfo.jsx b/src/components/Project/ManageProjectInfo.jsx index eaf7cacd..a9490675 100644 --- a/src/components/Project/ManageProjectInfo.jsx +++ b/src/components/Project/ManageProjectInfo.jsx @@ -1,11 +1,24 @@ import React, { useEffect, useState } from "react"; +import { projectSchema, projectDefault } from "./ProjectSchema"; import { useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; + import Label from "../common/Label"; import DatePicker from "../common/DatePicker"; +import { useCreateProject, useProjectDetails, useUpdateProject } from "../../hooks/useProjects"; -const currentDate = new Date().toLocaleDateString('en-CA'); +import { + DEFAULT_EMPTY_STATUS_ID, + ITEMS_PER_PAGE, + PROJECT_STATUS, +} from "../../utils/constants"; +import { + useOrganizationModal, + useOrganizationsList, +} from "../../hooks/useOrganization"; +import { localToUtc } from "../../utils/appUtils"; + +const currentDate = new Date().toLocaleDateString("en-CA"); const formatDate = (date) => { if (!date) { return currentDate; @@ -14,56 +27,23 @@ const formatDate = (date) => { if (isNaN(d.getTime())) { return currentDate; } - return d.toLocaleDateString('en-CA'); + return d.toLocaleDateString("en-CA"); }; -const ManageProjectInfo = ({ project, handleSubmitForm, onClose, isPending }) => { - const [CurrentProject, setCurrentProject] = useState(); +const ManageProjectInfo = ({ project, onClose }) => { const [addressLength, setAddressLength] = useState(0); const maxAddressLength = 500; + const { onOpen, startStep, flowType } = useOrganizationModal(); 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() } : {}), - name: z.string().min(1, { message: "Project Name is required" }), - shortName: z.string().optional(), - contactPerson: z - .string() - .min(1, { message: "Contact Person Name is required" }) - .regex(/^[A-Za-z\s]+$/, { - message: "Contact Person must contain only letters", - }), - projectAddress: z - .string() - .min(1, { message: "Address is required" }) - .max(500, "Address must not exceed 150 characters"), - startDate: z - .string() - .min(1, { message: "Start Date is required" }) - .default(currentDate), - endDate: z - .string() - .min(1, { message: "End Date is required" }) - .default(currentDate), - projectStatusId: z - .string() - .min(1, { message: "Status is required" }), - promoterId:z.string().min(1,{message:"Promoter is required"}), - pmcId:z.string().min(1,{message:"PMC is required"}) - }) - .refine( - (data) => { - const start = new Date(data.startDate); - const end = new Date(data.endDate); - return end >= start; - }, - { - path: ["endDate"], // attaches the error to the endDate field - message: "End Date must be greater than Start Date", - } - ); + const { projects_Details, loading } = useProjectDetails(project); + const { data, isLoading, isError, error } = useOrganizationsList( + ITEMS_PER_PAGE, + 1, + true + ); + const { mutate: UpdateProject, isPending } = useUpdateProject(() => {onClose?.()}); + const {mutate:CeateProject,isPending:isCreating}= useCreateProject(()=>onClose?.()) const { register, @@ -74,81 +54,67 @@ const ManageProjectInfo = ({ project, handleSubmitForm, onClose, isPending }) => getValues, } = useForm({ resolver: zodResolver(projectSchema), - defaultValues: { - id: project?.id || "", - name: project?.name || "", - shortName: project?.shortName || "", - contactPerson: project?.contactPerson || "", - projectAddress: project?.projectAddress || "", - startDate: formatDate(project?.startDate) || currentDate, - endDate: formatDate(project?.endDate) || currentDate, - projectStatusId: project?.projectStatusId && project.projectStatusId !== DEFAULT_EMPTY_STATUS_ID - - ? String(project.projectStatusId) - - : ACTIVE_STATUS_ID, - promoterId:project.promoterId, - pmcId:project.pmcId - }, + defaultValues: projectDefault, mode: "onChange", }); useEffect(() => { - setCurrentProject(project); - 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?.projectStatus?.id) || "00000000-0000-0000-0000-000000000000", - } - : {} - ); - setAddressLength(project?.projectAddress?.length || 0); - }, [project, reset]); + if (project && projects_Details) + reset({ + name: projects_Details?.name || "", + shortName: projects_Details?.shortName || "", + contactPerson: projects_Details?.contactPerson || "", + projectAddress: projects_Details?.projectAddress || "", + startDate: formatDate(projects_Details?.startDate) || "", + endDate: formatDate(projects_Details?.endDate) || "", + projectStatusId: + String(projects_Details?.projectStatus?.id) || + DEFAULT_EMPTY_STATUS_IDF, + promoterId: projects_Details.promoter.id || "", + pmcId: projects_Details.pmc.id || "", + }); + setAddressLength(projects_Details?.projectAddress?.length || 0); + }, [project, projects_Details, reset]); - /** - - * Handles the form submission. - - * @param {object} updatedProject - The project data from the form. - - */ - - const onSubmitForm = (updatedProject) => { - - handleSubmitForm(updatedProject); + const onSubmitForm = (formData) => { + if (project) { + let payload = { + ...formData, + startDate: localToUtc(formData.startDate), + endDate: localToUtc(formData.endDate), + id: project, + }; + console.log(payload); + UpdateProject({ projectId: project, payload: payload }); + }else{ + let payload = { + ...formData, + startDate: localToUtc(formData.startDate), + endDate: localToUtc(formData.endDate), + }; + CeateProject(payload) + } }; const handleCancel = () => { - reset({ - id: project?.id || "", - name: project?.name || "", - shortName: project?.shortName || "", - contactPerson: project?.contactPerson || "", - projectAddress: project?.projectAddress || "", - startDate: formatDate(project?.startDate) || currentDate, - endDate: formatDate(project?.endDate) || currentDate, - projectStatusId: String(project?.projectStatus?.id || "00000000-0000-0000-0000-000000000000"), - }); + reset(projectDefault); onClose(); }; + const handleOrganizaioFinder = () => { + onClose(); + onOpen({ startStep: 2, flowType: "default" }); + }; + return ( -
-
-
- {project?.id ? "Edit Project" : "Create Project"} -
+
{project ? "Edit Project" : "Create Project"}
-
+
- {/*
-
*/}
@@ -278,4 +222,4 @@ const ProjectCard = ({ projectData, recall }) => { ); }; -export default ProjectCard; \ No newline at end of file +export default ProjectCard; diff --git a/src/components/Project/ProjectCardView.jsx b/src/components/Project/ProjectCardView.jsx new file mode 100644 index 00000000..1108120c --- /dev/null +++ b/src/components/Project/ProjectCardView.jsx @@ -0,0 +1,70 @@ +import React from 'react' +import { useProjects } from '../../hooks/useProjects' +import Loader from '../common/Loader' +import ProjectCard from './ProjectCard' + +const ProjectCardView = ({currentItems,setCurrentPage,totalPages }) => { + + + return ( + +
+ + { currentItems.length === 0 && ( +

No projects found.

+ )} + + {currentItems.map((project) => ( + + ))} + + + { totalPages > 1 && ( + + )} +
+ + ) +} + +export default ProjectCardView diff --git a/src/components/Project/ProjectListView.jsx b/src/components/Project/ProjectListView.jsx new file mode 100644 index 00000000..2e717163 --- /dev/null +++ b/src/components/Project/ProjectListView.jsx @@ -0,0 +1,280 @@ +import React, { useState } from "react"; +import { MANAGE_PROJECT, PROJECT_STATUS } from "../../utils/constants"; +import { useProjects } from "../../hooks/useProjects"; +import { formatNumber, formatUTCToLocalTime } from "../../utils/dateUtils"; +import ProgressBar from "../common/ProgressBar"; +import { + getProjectStatusColor, + getProjectStatusName, +} from "../../utils/projectStatus"; +import { useDispatch } from "react-redux"; +import { setProjectId } from "../../slices/localVariablesSlice"; +import { useNavigate } from "react-router-dom"; +import { useHasUserPermission } from "../../hooks/useHasUserPermission"; +import { useProjectContext } from "../../pages/project/ProjectPage"; +import usePagination from "../../hooks/usePagination"; + +const ProjectListView = ({ + currentItems, + selectedStatuses, + handleStatusChange, + setCurrentPage, + totalPages, + isLoading, +}) => { + const dispatch = useDispatch(); + const navigate = useNavigate(); + const { setMangeProject } = useProjectContext(); + // const { data, isLoading, isError, error } = useProjects(); + + // check Permissions + const canManageProject = useHasUserPermission(MANAGE_PROJECT); + + const projectColumns = [ + { + key: "projectName", + label: "Project Name", + className: "text-start py-3", + getValue: (p) => ( +
{ + dispatch(setProjectId(p.id)); + navigate(`/projects/details`); + }} + > + {p.shortName ? `${p.name} (${p.shortName})` : p.name} +
+ ), + }, + { + key: "contactPerson", + label: "Contact Person", + className: "text-start small", + getValue: (p) => `${p?.contactPerson ?? ""}`.trim() || "N/A", + }, + { + key: "startDate", + label: "Start Date", + className: "text-center small", + getValue: (p) => formatUTCToLocalTime(p?.startDate) || "N/A", + }, + { + key: "deadline", + label: "Deadline", + className: "text-center small", + getValue: (p) => formatUTCToLocalTime(p?.endDate) || "N/A", + }, + { + key: "task", + label: "Task", + colSpan: 2, + className: "text-center small", + getValue: (p) => formatNumber(p?.plannedWork) || "0", + }, + { + key: "progress", + label: "Progress", + className: "text-start small", + + getValue: (p) => ( + + ), + }, + { + key: "status", + label: "Status", + className: "text-center small", + isFilter: true, + customRender: (_, selectedStatuses, handleStatusChange) => ( +
+ +
    + {PROJECT_STATUS.map(({ id, label }) => ( +
  • +
    + handleStatusChange(id)} + /> + +
    +
  • + ))} +
+
+ ), + getValue: (p) => ( + + {getProjectStatusName(p.projectStatusId)} + + ), + }, + ]; + + const handleViewActivities = (project) => { + dispatch(setProjectId(project)); + navigate(`/activities/records?project=${project}`); + }; + + return ( +
+ + + + {projectColumns.map((col) => ( + + ))} + + + + + {currentItems?.map((project) => ( + + {projectColumns.map((col) => ( + + ))} + + + ))} + +
+ {col.label} + Action
+ {col.getValue + ? col.getValue(project) + : project[col.key] || "N/A"} + + +
+ + {isLoading && ( +
+ {" "} + {isLoading &&

Loading...

} + {!isLoading && filteredProjects.length === 0 && ( +

No projects found.

+ )} +
+ )} + {!isLoading && currentItems.length === 0 && ( +
+

No projects found.

+
+ )} + {!isLoading && totalPages > 1 && ( + + )} +
+ ); +}; + +export default ProjectListView; diff --git a/src/components/Project/ProjectModal.jsx b/src/components/Project/ProjectModal.jsx index b56720da..788ceabf 100644 --- a/src/components/Project/ProjectModal.jsx +++ b/src/components/Project/ProjectModal.jsx @@ -1,36 +1,35 @@ -import React from 'react' -import AssignRole from './AssignTask' +import React from "react"; +import AssignRole from "./AssignTask"; -const ProjectModal = ({modalConfig,closeModal}) => { - +const ProjectModal = ({ modalConfig, closeModal }) => { return ( - ) -} + className="modal fade" + id="project-modal" + tabindex="-1" + aria-hidden="true" + role="dialog" + > +
+
+
+ +
-export default ProjectModal \ No newline at end of file + {modalConfig?.type === "assignRole" && ( + + )} +
+
+
+
+ ); +}; + +export default ProjectModal; diff --git a/src/components/Project/ProjectOrganization/ProjectAssignedOrgs.jsx b/src/components/Project/ProjectOrganization/ProjectAssignedOrgs.jsx index 2aa2b046..97f54cf0 100644 --- a/src/components/Project/ProjectOrganization/ProjectAssignedOrgs.jsx +++ b/src/components/Project/ProjectOrganization/ProjectAssignedOrgs.jsx @@ -67,7 +67,7 @@ const ProjectAssignedOrgs = () => { return (
-
+
diff --git a/src/components/Project/ProjectOverview.jsx b/src/components/Project/ProjectOverview.jsx index f5cb219e..ee665144 100644 --- a/src/components/Project/ProjectOverview.jsx +++ b/src/components/Project/ProjectOverview.jsx @@ -10,9 +10,9 @@ import ReactApexChart from "react-apexcharts"; import Chart from "react-apexcharts"; const ProjectOverview = ({ project }) => { - const { projects } = useProjects(); + const { data } = useProjects(); const [current_project, setCurrentProject] = useState( - projects.find((pro) => pro.id == project) + data?.find((pro) => pro.id == project) ); const selectedProject = useSelector( @@ -154,7 +154,7 @@ const ProjectOverview = ({ project }) => { }, [current_project]); useEffect(() => { - setCurrentProject(projects.find((pro) => pro.id == selectedProject)); + setCurrentProject(data?.find((pro) => pro.id == selectedProject)); if (current_project) { let val = getProgressInPercentage( current_project.plannedWork, diff --git a/src/components/Project/ProjectSchema.jsx b/src/components/Project/ProjectSchema.jsx new file mode 100644 index 00000000..6caafe81 --- /dev/null +++ b/src/components/Project/ProjectSchema.jsx @@ -0,0 +1,59 @@ +import { z } from "zod"; +import { DEFAULT_EMPTY_STATUS_ID } from "../../utils/constants"; +const currentDate = new Date() + + +export const projectDefault = { + name: "", + shortName: "", + contactPerson: "", + projectAddress: "", + startDate: currentDate.toISOString().split("T")[0], + endDate: currentDate.toISOString().split("T")[0], + projectStatusId: DEFAULT_EMPTY_STATUS_ID, + promoterId: "", + pmcId: "", +}; + + +export const projectSchema = z + .object({ + name: z.string().min(1, { message: "Project Name is required" }), + shortName: z.string().optional(), + contactPerson: z + .string() + .min(1, { message: "Contact Person Name is required" }) + .regex(/^[A-Za-z\s]+$/, { + message: "Contact Person must contain only letters", + }), + projectAddress: z + .string() + .min(1, { message: "Address is required" }) + .max(500, "Address must not exceed 150 characters"), + startDate: z + .string() + .min(1, { message: "Start Date is required" }) + .default(projectDefault), + endDate: z + .string() + .min(1, { message: "End Date is required" }) + .default(projectDefault), + projectStatusId: z.string().min(1, { message: "Status is required" }), + promoterId: z.string().min(1, { message: "Promoter is required" }), + pmcId: z.string().min(1, { message: "PMC is required" }), + }) + .refine( + (data) => { + const start = new Date(data.startDate); + const end = new Date(data.endDate); + return end >= start; + }, + { + path: ["endDate"], + message: "End Date must be greater than Start Date", + } + ); + + + + diff --git a/src/components/Project/Team/Teams.jsx b/src/components/Project/Team/Teams.jsx index 81921bd9..473dfae6 100644 --- a/src/components/Project/Team/Teams.jsx +++ b/src/components/Project/Team/Teams.jsx @@ -1,5 +1,4 @@ import React, { useState, useEffect, useCallback, useMemo } from "react"; -import MapUsers from "../MapUsers"; import { Link, NavLink, useNavigate, useParams } from "react-router-dom"; import showToast from "../../../services/toastService"; diff --git a/src/components/common/DatePicker.jsx b/src/components/common/DatePicker.jsx index acb9007f..6b73dc36 100644 --- a/src/components/common/DatePicker.jsx +++ b/src/components/common/DatePicker.jsx @@ -1,14 +1,13 @@ import { useEffect, useRef } from "react"; import { useController } from "react-hook-form"; - const DatePicker = ({ name, control, placeholder = "DD-MM-YYYY", className = "", allowText = false, - maxDate, // removed default new Date() + maxDate, minDate, ...rest }) => { @@ -22,43 +21,43 @@ const DatePicker = ({ }); useEffect(() => { - if (inputRef.current) { - flatpickr(inputRef.current, { - dateFormat: "d-m-Y", - allowInput: allowText, - defaultDate: value - ? flatpickr.parseDate(value, "Y-m-d") - : null, - maxDate: maxDate ?? undefined, // only applied if passed - minDate: minDate ? new Date(minDate.split("T")[0]) : undefined, - onChange: function (selectedDates) { - if (selectedDates.length > 0) { - // store in YYYY-MM-DD - const formatted = flatpickr.formatDate(selectedDates[0], "Y-m-d"); - onChange(formatted); - } else { - onChange(""); - } - }, - ...rest - }); - } + if (!inputRef.current) return; + + const fp = flatpickr(inputRef.current, { + dateFormat: "d-m-Y", + allowInput: allowText, + defaultDate: value ? new Date(value) : null, // safely convert to Date + maxDate: maxDate ? new Date(maxDate) : undefined, + minDate: minDate ? new Date(minDate) : undefined, + onChange: (selectedDates) => { + if (selectedDates.length > 0) { + onChange(flatpickr.formatDate(selectedDates[0], "Y-m-d")); + } else { + onChange(""); + } + }, + ...rest + }); + + return () => { + fp.destroy(); // clean up on unmount + }; }, [inputRef, value, allowText, maxDate, minDate, rest, onChange]); + const displayValue = value ? flatpickr.formatDate(new Date(value), "d-m-Y") : ""; + return ( -
+
{ + if (allowText) { + onChange(e.target.value); // allow manual typing if enabled + } + }} ref={(el) => { inputRef.current = el; ref(el); @@ -70,7 +69,7 @@ const DatePicker = ({ { - if (inputRef.current && inputRef.current._flatpickr) { + if (inputRef.current?._flatpickr) { inputRef.current._flatpickr.open(); } }} diff --git a/src/hooks/useAuth.jsx b/src/hooks/useAuth.jsx index 8f158197..8e5b8de2 100644 --- a/src/hooks/useAuth.jsx +++ b/src/hooks/useAuth.jsx @@ -12,6 +12,7 @@ import { closeAuthModal, openAuthModal, } from "../slices/localVariablesSlice.jsx"; +import { removeSession } from "../utils/authUtils.js"; export const useTenants = () => { return useQuery({ @@ -28,18 +29,24 @@ export const useSelectTenant = (onSuccessCallBack) => { const res = await AuthRepository.selectTenant(tenantId); return res.data; }, - + onSuccess: (data) => { - localStorage.setItem("jwtToken", data.token); - localStorage.setItem("refreshToken", data.refreshToken); + if (localStorage.getItem("jwtToken")) { + localStorage.setItem("jwtToken", data.token); + localStorage.setItem("refreshToken", data.refreshToken); + } else { + sessionStorage.setItem("jwtToken", data.token); + sessionStorage.setItem("refreshToken", data.refreshToken); + } + if (onSuccessCallBack) onSuccessCallBack(); }, onError: (error) => { showToast(error.message || "Error while creating project", "error"); localStorage.removeItem("jwtToken"); - localStorage.removeItem("refreshToken") - localStorage.removeItem("ctnt") + localStorage.removeItem("refreshToken"); + localStorage.removeItem("ctnt"); }, }); }; @@ -55,29 +62,26 @@ export const useAuthModal = () => { }; }; -export const useLogout = ()=>{ + +export const useLogout = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: async () => { - let payload = {refreshToken: localStorage.getItem("refreshToken")} - return await AuthRepository.logout(payload); + let payload = { refreshToken: localStorage.getItem("refreshToken") || sessionStorage.getItem("refreshToken") }; + return await AuthRepository.logout(payload); }, - + onSuccess: (data) => { - localStorage.removeItem("jwtToken"); - localStorage.removeItem("refreshToken"); - localStorage.removeItem("ctnt"); - localStorage.clear(); - window.location.href = "/auth/login"; + removeSession() + + window.location.href = "/auth/login"; if (onSuccessCallBack) onSuccessCallBack(); }, onError: (error) => { showToast(error.message || "Error while creating project", "error"); - localStorage.removeItem("jwtToken"); - localStorage.removeItem("refreshToken") - localStorage.removeItem("ctnt") + removeSession() }, }); -} \ No newline at end of file +}; diff --git a/src/hooks/useEmployees.js b/src/hooks/useEmployees.js index 58a12e5a..477b18f7 100644 --- a/src/hooks/useEmployees.js +++ b/src/hooks/useEmployees.js @@ -221,7 +221,7 @@ export const useUpdateEmployee = () => { mutationFn: (employeeData) => EmployeeRepository.manageEmployee(employeeData), onSuccess: (_, variables) => { - const id = variables.id || variables.employeeId; + const id = variables?.id || variables?.employeeId; const isAllEmployee = variables.IsAllEmployee; // Cache invalidation diff --git a/src/hooks/useOrganization.js b/src/hooks/useOrganization.js index d6c93693..2248f7bc 100644 --- a/src/hooks/useOrganization.js +++ b/src/hooks/useOrganization.js @@ -37,6 +37,13 @@ export const useOrganizationModal = () => { // ================================Query============================================================= +export const useOrganization=(id)=>{ +return useQuery({ + queryKey:["organization",id], + queryFn:async()=> await OrganizationRepository.getOrganizaion(id), + enabled:!!id +}) +} export const useOrganizationBySPRID = (sprid) => { return useQuery({ queryKey: ["organization by", sprid], @@ -167,11 +174,11 @@ export const useAssignOrgToTenant = (onSuccessCallback) => { export const useUpdateOrganization = () => { const useClient = useQueryClient(); return useMutation({ - mutationFn: async (payload) => - await OrganizationRepository.assignOrganizationToProject(payload), + mutationFn: async ({orgId,payload}) => + await OrganizationRepository.updateOrganizaion(orgId,payload), onSuccess: (_, variables) => { - // useClient.invalidateQueries({ queryKey: ["organizationList"] }); - showToast("Organization successfully", "success"); + useClient.invalidateQueries({ queryKey: ["organizationList"] }); + showToast("Organization Updated successfully", "success"); if (onSuccessCallback) onSuccessCallback(); }, onError: (error) => { diff --git a/src/hooks/useProjects.js b/src/hooks/useProjects.js index a0116a62..28c65a0b 100644 --- a/src/hooks/useProjects.js +++ b/src/hooks/useProjects.js @@ -14,27 +14,15 @@ import { } from "@tanstack/react-query"; import showToast from "../services/toastService"; - - -export const useCurrentService = ()=>{ - return useSelector((store)=>store.globalVariables.selectedServiceId) -} - - - - +export const useCurrentService = () => { + return useSelector((store) => store.globalVariables.selectedServiceId); +}; // ------------------------------Query------------------- export const useProjects = () => { const loggedUser = useSelector((store) => store.globalVariables.loginUser); - - const { - data: projects = [], - isLoading: loading, - error, - refetch, - } = useQuery({ + return useQuery({ queryKey: ["ProjectsList"], queryFn: async () => { const response = await ProjectRepository.getProjectList(); @@ -42,19 +30,13 @@ export const useProjects = () => { }, enabled: !!loggedUser, }); - - return { - projects, - loading, - error, - refetch, - }; }; export const useEmployeesByProjectAllocated = ( projectId, serviceId, - organizationId,emloyeeeStatus + organizationId, + emloyeeeStatus ) => { const { data = [], @@ -62,16 +44,23 @@ export const useEmployeesByProjectAllocated = ( refetch, error, } = useQuery({ - queryKey: ["empListByProjectAllocated", projectId, serviceId,organizationId,emloyeeeStatus], + queryKey: [ + "empListByProjectAllocated", + projectId, + serviceId, + organizationId, + emloyeeeStatus, + ], queryFn: async () => { const res = await ProjectRepository.getProjectAllocation( - projectId, serviceId, + projectId, + serviceId, organizationId, - emloyeeeStatus + emloyeeeStatus ); return res?.data || res; }, - enabled: !!projectId, + enabled: !!projectId, onError: (error) => { showToast( error.message || "Error while fetching project allocated employees", @@ -190,17 +179,20 @@ export const useProjectName = () => { }; }; -export const useProjectInfra = (projectId,serviceId) => { +export const useProjectInfra = (projectId, serviceId) => { const { data: projectInfra, isLoading, error, isFetched, } = useQuery({ - queryKey: ["ProjectInfra", projectId,serviceId], + queryKey: ["ProjectInfra", projectId, serviceId], queryFn: async () => { if (!projectId) return null; - const res = await ProjectRepository.getProjectInfraByproject(projectId,serviceId); + const res = await ProjectRepository.getProjectInfraByproject( + projectId, + serviceId + ); return res.data; }, enabled: !!projectId, @@ -212,11 +204,18 @@ export const useProjectInfra = (projectId,serviceId) => { return { projectInfra, isLoading, error, isFetched }; }; -export const useProjectTasks = (workAreaId, serviceId = null, isExpandedArea = false) => { +export const useProjectTasks = ( + workAreaId, + serviceId = null, + isExpandedArea = false +) => { const { data, isLoading, error } = useQuery({ queryKey: ["WorkItems", workAreaId, serviceId], queryFn: async () => { - const res = await ProjectRepository.getProjectTasksByWorkArea(workAreaId, serviceId); + const res = await ProjectRepository.getProjectTasksByWorkArea( + workAreaId, + serviceId + ); return res.data; // return actual task list }, enabled: !!workAreaId && isExpandedArea, // only fetch if workAreaId exists and area is expanded @@ -308,13 +307,21 @@ export const useProjectAssignedServices = (projectId) => { }); }; - -export const useEmployeeForTaskAssign = (projectId,serviceId,organizationId)=>{ - return useQuery({ - queryKey:["EmployeeForTaskAssign",projectId,serviceId,organizationId], - queryFn:async()=> await ProjectRepository.getEmployeeForTaskAssign(projectId,serviceId,organizationId) - }) -} +export const useEmployeeForTaskAssign = ( + projectId, + serviceId, + organizationId +) => { + return useQuery({ + queryKey: ["EmployeeForTaskAssign", projectId, serviceId, organizationId], + queryFn: async () => + await ProjectRepository.getEmployeeForTaskAssign( + projectId, + serviceId, + organizationId + ), + }); +}; // -- -------------Mutation------------------------------- @@ -322,8 +329,8 @@ export const useCreateProject = ({ onSuccessCallback }) => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async (newProject) => { - const res = await ProjectRepository.manageProject(newProject); + mutationFn: async (payload) => { + const res = await ProjectRepository.manageProject(payload); return res.data; }, onSuccess: (data) => { @@ -349,11 +356,11 @@ export const useCreateProject = ({ onSuccessCallback }) => { }); }; -export const useUpdateProject = ({ onSuccessCallback }) => { +export const useUpdateProject = ( onSuccessCallback ) => { const queryClient = useQueryClient(); const { mutate, isPending, isSuccess, isError } = useMutation({ - mutationFn: async ({ projectId, updatedData }) => { - return await ProjectRepository.updateProject(projectId, updatedData); + mutationFn: async ({ projectId, payload }) => { + return await ProjectRepository.updateProject(projectId, payload); }, onSuccess: (data, variables) => { @@ -413,7 +420,7 @@ export const useManageProjectAllocation = ({ const queryClient = useQueryClient(); const { mutate, isPending, isSuccess, isError } = useMutation({ - mutationFn: async ({payload}) => { + mutationFn: async ({ payload }) => { const response = await ProjectRepository.manageProjectAllocation(payload); return response.data; }, diff --git a/src/pages/authentication/LoginPage.jsx b/src/pages/authentication/LoginPage.jsx index 363de91f..6be7cb2b 100644 --- a/src/pages/authentication/LoginPage.jsx +++ b/src/pages/authentication/LoginPage.jsx @@ -16,13 +16,13 @@ const LoginPage = () => { const loginSchema = IsLoginWithOTP ? z.object({ - username: z.string().trim().email({ message: "Valid email required" }), - }) + username: z.string().trim().email({ message: "Valid email required" }), + }) : z.object({ - username: z.string().trim().email({ message: "Valid email required" }), - password: z.string().trim().min(1, { message: "Password required" }), - rememberMe: z.boolean(), - }); + username: z.string().trim().email({ message: "Valid email required" }), + password: z.string().trim().min(1, { message: "Password required" }), + rememberMe: z.boolean(), + }); const { register, @@ -41,8 +41,13 @@ const LoginPage = () => { password: data.password, }; const response = await AuthRepository.login(userCredential); - localStorage.setItem("jwtToken", response.data.token); - localStorage.setItem("refreshToken", response.data.refreshToken); + if (data.rememberMe) { + localStorage.setItem("jwtToken", response.data.token); + localStorage.setItem("refreshToken", response.data.refreshToken); + } else { + sessionStorage.setItem("jwtToken", response.data.token); + sessionStorage.setItem("refreshToken", response.data.refreshToken); + } setLoading(false); navigate("/auth/switch/org"); } else { @@ -69,6 +74,16 @@ const LoginPage = () => { } }, [IsLoginWithOTP]); + useEffect(() => { + const token = + localStorage.getItem("jwtToken") || + sessionStorage.getItem("jwtToken"); + + if (token) { + navigate("/dashboard", { replace: true }); + } +}, []); + return (
@@ -106,36 +121,6 @@ const LoginPage = () => { {/* Password */} {!IsLoginWithOTP && ( <> - {/*
- -
- - setHidepass(!hidepass)} - > - - -
- {errors.password && ( -
- {errors.password.message} -
- )} -
*/} -
- {/* ✅ Error message */} {errors.password && ( -
+
{errors.password.message}
)}
- {/* Remember Me + Forgot Password */}
@@ -209,8 +196,8 @@ const LoginPage = () => { {loading ? "Please Wait..." : IsLoginWithOTP - ? "Send OTP" - : "Sign In"} + ? "Send OTP" + : "Sign In"} {/* Login With OTP Button */} @@ -254,4 +241,4 @@ const LoginPage = () => { ); }; -export default LoginPage; \ No newline at end of file +export default LoginPage; diff --git a/src/pages/authentication/TenantSelectionPage.jsx b/src/pages/authentication/TenantSelectionPage.jsx index 22b72292..18028902 100644 --- a/src/pages/authentication/TenantSelectionPage.jsx +++ b/src/pages/authentication/TenantSelectionPage.jsx @@ -18,7 +18,7 @@ const TenantSelectionPage = () => { chooseTenant(tenantId); }; - const {mutate:handleLogout,isPending:isLogouting} = useLogout(()=>{}) + const {mutate:handleLogout,isPending:isLogouting} = useLogout() useEffect(() => { diff --git a/src/pages/employee/EmployeeList.jsx b/src/pages/employee/EmployeeList.jsx index b0f10a75..fc77ba1c 100644 --- a/src/pages/employee/EmployeeList.jsx +++ b/src/pages/employee/EmployeeList.jsx @@ -751,7 +751,6 @@ const EmployeeList = () => {
) : ( - //
diff --git a/src/pages/project/ProjectList.jsx b/src/pages/project/ProjectList.jsx deleted file mode 100644 index b3b1ae7d..00000000 --- a/src/pages/project/ProjectList.jsx +++ /dev/null @@ -1,419 +0,0 @@ -import React, { useState, useEffect, useCallback } from "react"; -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, useCreateProject } from "../../hooks/useProjects"; -import showToast from "../../services/toastService"; -import { useHasUserPermission } from "../../hooks/useHasUserPermission"; -import { useProfile } from "../../hooks/useProfile"; -import { ITEMS_PER_PAGE, MANAGE_PROJECT } from "../../utils/constants"; -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"; -import { useDispatch, useSelector } from "react-redux"; -import { setProjectId } from "../../slices/localVariablesSlice"; - -const ProjectList = () => { - const { profile: loginUser } = useProfile(); - const [listView, setListView] = useState(false); - const [showModal, setShowModal] = useState(false); - const selectedProject = useSelector( - (store) => store.localVariables.projectId - ); - const dispatch = useDispatch(); - - const { projects, loading, error, refetch } = useProjects(); - const [projectList, setProjectList] = useState([]); - - 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", - ]); - - const handleShow = () => setShowModal(true); - const handleClose = () => setShowModal(false); - useEffect(() => { - dispatch(setProjectId(null)); - }, []); - const sortingProject = (projects) => { - if (!loading && Array.isArray(projects)) { - const grouped = {}; - projects.forEach((project) => { - const statusId = project.projectStatusId; - if (!grouped[statusId]) grouped[statusId] = []; - grouped[statusId].push(project); - }); - - const sortedGrouped = selectedStatuses - .filter((statusId) => grouped[statusId]) - .flatMap((statusId) => - grouped[statusId].sort((a, b) => - a.name.toLowerCase().localeCompare(b.name.toLowerCase()) - ) - ); - - setProjectList((prev) => { - const isSame = JSON.stringify(prev) === JSON.stringify(sortedGrouped); - return isSame ? prev : sortedGrouped; - }); - } - }; - - useEffect(() => { - if (!loading && projects) { - sortingProject(projects); - } - }, [projects, loading, selectedStatuses]); - - useEffect(() => { - setHasManageProject(loginUser ? HasManageProjectPermission : false); - }, [loginUser, HasManageProjectPermission]); - - const handleSubmitForm = (newProject) => { - createProject(newProject); - }; - - const handleStatusChange = (statusId) => { - setCurrentPage(1); - setSelectedStatuses((prev) => - prev.includes(statusId) - ? prev.filter((id) => id !== statusId) - : [...prev, statusId] - ); - }; - - const handleStatusFilterFromChild = (statusesFromChild) => { - setSelectedStatuses(statusesFromChild); - }; - - const filteredProjects = projectList.filter((project) => { - const matchesStatus = selectedStatuses.includes(project.projectStatusId); - const matchesSearch = project.name - .toLowerCase() - .includes(searchTerm.toLowerCase()); - return matchesStatus && matchesSearch; - }); - - const totalPages = Math.ceil(filteredProjects.length / ITEMS_PER_PAGE); - - const { currentItems, currentPage, paginate, setCurrentPage } = usePagination( - filteredProjects, - ITEMS_PER_PAGE - ); - - useEffect(() => { - const tooltipTriggerList = Array.from( - document.querySelectorAll('[data-bs-toggle="tooltip"]') - ); - tooltipTriggerList.forEach((el) => new bootstrap.Tooltip(el)); - }, []); - - return ( - <> - {showModal && ( - - - - )} - -
- -
-
-
-
-
- { - setSearchTerm(e.target.value); - setCurrentPage(1); - }} - /> -
- -
- - -
- -
- -
    - {[ - { - 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", - }, - { - id: "ef1c356e-0fe0-42df-a5d3-8daee355492d", - label: "Inactive", - }, - { - id: "33deaef9-9af1-4f2a-b443-681ea0d04f81", - label: "Completed", - }, - ].map(({ id, label }) => ( -
  • -
    - handleStatusChange(id)} - /> - -
    -
  • - ))} -
-
-
- -
- -
-
-
-
- {loading &&

Loading...

} - {!loading && filteredProjects.length === 0 && !listView && ( -

No projects found.

- )} - - {listView ? ( -
-
-
-
- - - - - - - - - - - - - - {currentItems.length === 0 ? ( - - - - ) : ( - currentItems.map((project) => ( - - )) - )} - -
- Project Name - Contact PersonSTART DATEDEADLINETaskProgress -
- -
    - {[ - { - 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", - }, - { - id: "ef1c356e-0fe0-42df-a5d3-8daee355492d", - label: "Inactive", - }, - { - id: "33deaef9-9af1-4f2a-b443-681ea0d04f81", - label: "Completed", - }, - ].map(({ id, label }) => ( -
  • -
    - handleStatusChange(id)} - /> - -
    -
  • - ))} -
-
-
- Action -
- No projects found -
-
{" "} -
{" "} -
- ) : ( -
- {currentItems.map((project) => ( - - ))} -
- )} - - {!loading && totalPages > 1 && ( - - )} -
- - ); -}; - -export default ProjectList; diff --git a/src/pages/project/ProjectListView.jsx b/src/pages/project/ProjectListView.jsx deleted file mode 100644 index ad47c735..00000000 --- a/src/pages/project/ProjectListView.jsx +++ /dev/null @@ -1,206 +0,0 @@ -import React, { useState, useEffect } from "react"; -import moment from "moment"; -import { - useProjectDetails, - useProjects, - useUpdateProject, -} from "../../hooks/useProjects"; -import { - getProjectStatusName, - getProjectStatusColor, -} from "../../utils/projectStatus"; -import ProgressBar from "../../components/common/ProgressBar"; -import { useNavigate } from "react-router-dom"; -import ManageProject from "../../components/Project/ManageProject"; -import ProjectRepository from "../../repositories/ProjectRepository"; -import { MANAGE_PROJECT } from "../../utils/constants"; -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"; -import { setProjectId } from "../../slices/localVariablesSlice"; -import { useDispatch } from "react-redux"; - -const ProjectListView = ({ projectData, recall }) => { - const [projectInfo, setProjectInfo] = useState(projectData); - const dispatch = useDispatch() - 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]); - const { - mutate: updateProject, - isPending, - isSuccess, - isError, - } = useUpdateProject({ - onSuccessCallback: () => { - setShowModal(false); - }, - }) - - const handleShow = async () => { - try { - const { data } = await refetch(); - setShowModal(true); - } catch (err) { - showToast("Failed to load project details", "error"); - } - }; - - const getProgress = (planned, completed) => { - return (completed * 100) / planned + "%"; - }; - const getProgressInNumber = (planned, completed) => { - return (completed * 100) / planned; - }; - - const handleClose = () => setShowModal(false); - - const handleViewProject = () => { - dispatch(setProjectId(projectInfo.id)) - navigate(`/projects/details`); - }; - - const handleViewActivities = () => { - dispatch(setProjectId(projectInfo.id)) - navigate(`/activities/records?project=${projectInfo.id}`); - }; - - const handleFormSubmit = (updatedProject) => { - if (projectInfo?.id) { - updateProject({ - projectId: projectInfo.id, - updatedData: updatedProject, - }); - } - }; - - return ( - <> - {showModal && projects_Details && ( - - )} - - - - { - dispatch(setProjectId(projectInfo.id)) - navigate(`/projects/details`) - }} - > - {projectInfo.shortName - ? `${projectInfo.name} (${projectInfo.shortName})` - : projectInfo.name} - - - {projectInfo.contactPerson} - - - {projectInfo.startDate - ? moment(projectInfo.startDate).format("DD-MMM-YYYY") - : "NA"} - - - - {projectInfo.endDate - ? moment(projectInfo.endDate).format("DD-MMM-YYYY") - : "NA"} - - {formatNumber(projectInfo.plannedWork)} - - - - - -

- - {getProjectStatusName(projectInfo.projectStatusId)} - -

- - - -
- - -
- - - - ); -}; - -export default ProjectListView; \ No newline at end of file diff --git a/src/pages/project/ProjectPage.jsx b/src/pages/project/ProjectPage.jsx new file mode 100644 index 00000000..4fdccc30 --- /dev/null +++ b/src/pages/project/ProjectPage.jsx @@ -0,0 +1,235 @@ +import React, { createContext, useContext, useEffect, useState } from "react"; +import Breadcrumb from "../../components/common/Breadcrumb"; +import { ITEMS_PER_PAGE, MANAGE_PROJECT, PROJECT_STATUS } from "../../utils/constants"; +import ProjectListView from "../../components/Project/ProjectListView"; +import GlobalModel from "../../components/common/GlobalModel"; +import ManageProjectInfo from "../../components/Project/ManageProjectInfo"; +import ProjectCardView from "../../components/Project/ProjectCardView"; +import usePagination from "../../hooks/usePagination"; +import { useProjects } from "../../hooks/useProjects"; +import Loader from "../../components/common/Loader"; +import { useHasUserPermission } from "../../hooks/useHasUserPermission"; + +const ProjectContext = createContext(); +export const useProjectContext = () => { + const context = useContext(ProjectContext); + if (!context) { + throw new Error("useProjectContext must be used within an ProjectProvider"); + } + return context; +}; + +const ProjectPage = () => { + const [manageProject, setMangeProject] = useState({ + isOpen: false, + Project: null, + }); + + const [projectList, setProjectList] = useState([]); + const [listView, setListView] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + const HasManageProject = useHasUserPermission(MANAGE_PROJECT); + + const [selectedStatuses, setSelectedStatuses] = useState( + PROJECT_STATUS.map((s) => s.id) + ); + + const { data, isLoading, isError, error } = useProjects(); + + const contextDispatcher = { + setMangeProject, + }; + + const filteredProjects = projectList.filter((project) => { + const matchesStatus = selectedStatuses.includes(project.projectStatusId); + const matchesSearch = project.name + .toLowerCase() + .includes(searchTerm.toLowerCase()); + return matchesStatus && matchesSearch; + }); + + const totalPages = Math.ceil(filteredProjects.length / ITEMS_PER_PAGE); + + const { currentItems, currentPage, paginate, setCurrentPage } = usePagination( + filteredProjects, + ITEMS_PER_PAGE + ); + + const handleStatusChange = (statusId) => { + setCurrentPage(1); + setSelectedStatuses((prev) => + prev.includes(statusId) + ? prev.filter((id) => id !== statusId) + : [...prev, statusId] + ); + }; + + const sortingProject = (projects) => { + if (!isLoading && Array.isArray(projects)) { + const grouped = {}; + + projects.forEach((project) => { + const statusId = project.projectStatusId; + if (!grouped[statusId]) grouped[statusId] = []; + grouped[statusId].push(project); + }); + + const sortedGrouped = selectedStatuses + .filter((statusId) => grouped[statusId]) + .flatMap((statusId) => + grouped[statusId].sort((a, b) => + a.name.toLowerCase().localeCompare(b.name.toLowerCase()) + ) + ); + + setProjectList((prev) => { + const isSame = JSON.stringify(prev) === JSON.stringify(sortedGrouped); + return isSame ? prev : sortedGrouped; + }); + } + }; + + useEffect(() => { + if (!isLoading && data) { + sortingProject(data); + } + }, [data, isLoading, selectedStatuses]); + + + if(isLoading) return
+ if(isError) return

{error.message}

+ return ( + +
+ + +
+
+
+
+
+ { + setSearchTerm(e.target.value); + setCurrentPage(1); + }} + /> +
+ +
+ + +
+ +
+ +
    + {PROJECT_STATUS.map(({ id, label }) => ( +
  • +
    + handleStatusChange(id)} + /> + +
    +
  • + ))} +
+
+
+ +
+ {HasManageProject && ( )} +
+
+
+
+ + {/* Project Render here */} + {listView ? ( + + ) : ( + + )} + + {/* ------------------ */} + + {/* Project Manage UPdate or create */} + + {manageProject.isOpen && ( + setMangeProject({ isOpen: false, Project: null })} + > + setMangeProject({ isOpen: false, Project: null })} + /> + + )} +
+
+ ); +}; + +export default ProjectPage; diff --git a/src/pages/project/ProjectsPage.jsx b/src/pages/project/ProjectsPage.jsx deleted file mode 100644 index 3bb73175..00000000 --- a/src/pages/project/ProjectsPage.jsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from "react"; -import Breadcrumb from "../../components/common/Breadcrumb"; -import { PROJECt_STATUS } from "../../utils/constants"; - -const ProjectsPage = () => { - return ( -
- - - -
-
-
-
-
- { - setSearchTerm(e.target.value); - setCurrentPage(1); - }} - /> -
- -
- - -
- -
- -
    - {PROJECt_STATUS.map(({ id, label }) => ( -
  • -
    - handleStatusChange(id)} - /> - -
    -
  • - ))} -
-
-
- -
- -
-
-
-
-
- ); -}; - -export default ProjectsPage; diff --git a/src/repositories/OrganizationRespository.jsx b/src/repositories/OrganizationRespository.jsx index 28bbf3ce..818cdb59 100644 --- a/src/repositories/OrganizationRespository.jsx +++ b/src/repositories/OrganizationRespository.jsx @@ -2,6 +2,8 @@ import { api } from "../utils/axiosClient"; const OrganizationRepository = { createOrganization: (data) => api.post("/api/Organization/create", data), + updateOrganizaion:(id,data)=>api.put(`/api/Organization/edit/${id}`), + getOrganizaion:(id)=>api.get(`/api/Organization/details/${id}`), getOrganizationList: (pageSize, pageNumber, active, sprid, searchString) => { return api.get( `/api/Organization/list?pageSize=${pageSize}&pageNumber=${pageNumber}&active=${active}&${ diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index 8083b9ba..823ebb3a 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -13,7 +13,6 @@ import ChangePasswordPage from "../pages/authentication/ChangePassword"; // Home & Protected Pages import Dashboard from "../components/Dashboard/Dashboard"; -import ProjectList from "../pages/project/ProjectList"; import ProjectDetails from "../pages/project/ProjectDetails"; import ManageProject from "../components/Project/ManageProject"; import EmployeeList from "../pages/employee/EmployeeList"; @@ -52,6 +51,7 @@ import OrganizationPage from "../pages/Organization/OrganizationPage"; import LandingPage from "../pages/Home/LandingPage"; import TenantSelectionPage from "../pages/authentication/TenantSelectionPage"; import DailyProgrssReport from "../pages/DailyProgressReport/DailyProgrssReport"; +import ProjectPage from "../pages/project/ProjectPage"; const router = createBrowserRouter( [ { @@ -79,7 +79,7 @@ const router = createBrowserRouter( element: , children: [ { path: "/dashboard", element: }, - { path: "/projects", element: }, + { path: "/projects", element: }, { path: "/projects/details", element: }, { path: "/project/manage/:projectId", element: }, { path: "/employees", element: }, diff --git a/src/router/ProtectedRoute.jsx b/src/router/ProtectedRoute.jsx index 68dd847f..d174652c 100644 --- a/src/router/ProtectedRoute.jsx +++ b/src/router/ProtectedRoute.jsx @@ -2,13 +2,69 @@ import React, { useState, useEffect } from "react"; import { Navigate, Outlet } from "react-router-dom"; import { jwtDecode } from "jwt-decode"; import AuthRepository from "../repositories/AuthRepository"; +import { removeSession } from "../utils/authUtils"; + +const isTokenExpired = (token) => { + if (!token) return true; + try { + const { exp } = jwtDecode(token); + return exp * 1000 < Date.now(); + } catch { + return true; + } +}; + +const validateToken = async () => { + const token = + localStorage.getItem("jwtToken") || + sessionStorage.getItem("jwtToken"); + + const refreshTokenStored = + localStorage.getItem("refreshToken") || + sessionStorage.getItem("refreshToken"); + + if (!refreshTokenStored){ + console.log("no refrh tokem"); + removeSession() + return false + }; + + if (isTokenExpired(token)) { + return await attemptTokenRefresh(refreshTokenStored); + } + + return true; +}; + +const attemptTokenRefresh = async (storedRefreshToken) => { + try { + const currentToken = + localStorage.getItem("jwtToken") || + sessionStorage.getItem("jwtToken"); + + const response = await AuthRepository.refreshToken({ + token: currentToken, + refreshToken: storedRefreshToken, + }); + + const { token: newToken, refreshToken: newRefreshToken } = response.data; + + if (localStorage.getItem("jwtToken")) { + localStorage.setItem("jwtToken", newToken); + localStorage.setItem("refreshToken", newRefreshToken); + } else { + sessionStorage.setItem("jwtToken", newToken); + sessionStorage.setItem("refreshToken", newRefreshToken); + } + + return true; + } catch (error) { + console.error("Token refresh failed:", error); + return false; + } +}; const ProtectedRoute = () => { - // const isAuthenticated = localStorage.getItem("jwtToken"); // Example authentication check - // // const isAuthenticated = true; - // isTokenValid(); - // return isAuthenticated ? : - const [isAuthenticated, setIsAuthenticated] = useState(null); useEffect(() => { @@ -21,80 +77,10 @@ const ProtectedRoute = () => { }, []); if (isAuthenticated === null) { - return
Loading...
; // Show a loader while checking + return
Loading...
; } return isAuthenticated ? : ; }; -// Function to check if the token is expired -const isTokenExpired = (token) => { - if (!token) return true; - try { - const { exp } = jwtDecode(token); - return exp * 1000 < Date.now(); // Check if expired - } catch (error) { - return true; // If decoding fails, treat as expired - } -}; - -// Function to validate and refresh the token if expired -export const validateToken = async () => { - const token = localStorage.getItem("jwtToken"); - const refreshTokenStored = localStorage.getItem("refreshToken"); - // If refresh token is absent, cannot proceed - if (!refreshTokenStored) { - console.warn("No refresh token available. Redirecting to login."); - return false; - } - - // If access token expired, try to refresh - if (isTokenExpired(token)) { - return await attemptTokenRefresh(refreshTokenStored); - } - return true; -}; - -// Attempt to refresh the access token -const attemptTokenRefresh = async (storedRefreshToken) => { - try { - const response = await AuthRepository.refreshToken({ - token: localStorage.getItem("jwtToken"), - refreshToken: storedRefreshToken, - }); - - localStorage.setItem("jwtToken", response.data.token); - localStorage.setItem("refreshToken", response.data.refreshToken); - return true; - // api - // .post("/api/auth/refresh-token", { - // token: localStorage.getItem("jwtToken"), - // refreshToken: refreshToken, - // }) - // .then((data) => { - // localStorage.setItem("jwtToken", response.data.token); - // localStorage.setItem("refreshToken", response.data.refreshToken); - // return true; - // }) - // .catch((error) => { - // console.error("Token refresh failed:", error); - // }); - - // const refreshToken = localStorage.getItem("refreshToken"); - // const response = await axiosClient.post(`/api/auth/refresh-token`, { - // token: localStorage.getItem("jwtToken"), - // refreshToken: refreshToken, - // }); - - // if (response.status === 200) { - // localStorage.setItem("jwtToken", response.data.token); - // localStorage.setItem("refreshToken", response.data.refreshToken); - // return true; - // } - } catch (error) { - console.error("Token refresh failed:", error); - return false; - } -}; - export default ProtectedRoute; diff --git a/src/utils/authUtils.js b/src/utils/authUtils.js index e69de29b..8e266583 100644 --- a/src/utils/authUtils.js +++ b/src/utils/authUtils.js @@ -0,0 +1,7 @@ +export const removeSession = () => { + localStorage.removeItem("jwtToken"); + localStorage.removeItem("refreshToken"); + sessionStorage.removeItem("jwtToken"); + sessionStorage.removeItem("refreshToken"); + localStorage.removeItem("ctnt"); +}; \ No newline at end of file diff --git a/src/utils/axiosClient.jsx b/src/utils/axiosClient.jsx index 62e9b3bb..1cc0c7cd 100644 --- a/src/utils/axiosClient.jsx +++ b/src/utils/axiosClient.jsx @@ -16,14 +16,15 @@ export const axiosClient = axios.create({ // Auto retry failed requests (e.g., network issues) axiosRetry(axiosClient, { retries: 3 }); - +debugger // Request Interceptor — Add Bearer token if required axiosClient.interceptors.request.use( async (config) => { const requiresAuth = config.authRequired !== false; // default to true if (requiresAuth) { - const token = localStorage.getItem("jwtToken"); + const token = + localStorage.getItem("jwtToken") || sessionStorage.getItem("jwtToken"); if (token) { config.headers["Authorization"] = `Bearer ${token}`; config._retry = true; @@ -72,7 +73,7 @@ axiosClient.interceptors.response.use( if (status === 401 && !isRefreshRequest) { originalRequest._retry = true; - const refreshToken = localStorage.getItem("refreshToken"); + const refreshToken = localStorage.getItem("refreshToken") || sessionStorage.getItem("refreshToken"); if ( !refreshToken || @@ -87,15 +88,20 @@ axiosClient.interceptors.response.use( try { // Refresh token call const res = await axiosClient.post("/api/Auth/refresh-token", { - token: localStorage.getItem("jwtToken"), + token: localStorage.getItem("jwtToken") || sessionStorage.getItem("jwtToken"), refreshToken, }); const { token, refreshToken: newRefreshToken } = res.data.data; // Save updated tokens - localStorage.setItem("jwtToken", token); - localStorage.setItem("refreshToken", newRefreshToken); + if (localStorage.getItem("jwtToken")) { + localStorage.setItem("jwtToken", token); + localStorage.setItem("refreshToken", newRefreshToken); + } else { + sessionStorage.setItem("jwtToken", token); + sessionStorage.setItem("refreshToken", newRefreshToken); + } startSignalR(); // Set Authorization header diff --git a/src/utils/constants.jsx b/src/utils/constants.jsx index 5d17337a..62c686c9 100644 --- a/src/utils/constants.jsx +++ b/src/utils/constants.jsx @@ -118,7 +118,7 @@ export const orgSize = [ { val: "500+", name: "500+" }, ]; -export const PROJECt_STATUS = [ +export const PROJECT_STATUS = [ { id: "b74da4c2-d07e-46f2-9919-e75e49b12731", label: "Active", @@ -140,6 +140,7 @@ export const PROJECt_STATUS = [ label: "Completed", }, ]; +export const DEFAULT_EMPTY_STATUS_ID = "00000000-0000-0000-0000-000000000000"; export const BASE_URL = process.env.VITE_BASE_URL;