diff --git a/src/components/Activities/CheckCheckOutForm.jsx b/src/components/Activities/CheckCheckOutForm.jsx index 1473f746..ab105d84 100644 --- a/src/components/Activities/CheckCheckOutForm.jsx +++ b/src/components/Activities/CheckCheckOutForm.jsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -9,6 +9,7 @@ import showToast from "../../services/toastService"; import { checkIfCurrentDate } from "../../utils/dateUtils"; import { useMarkAttendance } from "../../hooks/useAttendance"; import { useSelectedProject } from "../../slices/apiDataManager"; +import { useProjectName } from "../../hooks/useProjects"; const createSchema = (modeldata) => { return z @@ -19,31 +20,36 @@ const createSchema = (modeldata) => { .max(200, "Description should be less than 200 characters") .optional(), }) - .refine((data) => { - if (modeldata?.checkInTime && !modeldata?.checkOutTime) { - const checkIn = new Date(modeldata.checkInTime); - const [time, modifier] = data.markTime.split(" "); - const [hourStr, minuteStr] = time.split(":"); - let hour = parseInt(hourStr, 10); - const minute = parseInt(minuteStr, 10); + .refine( + (data) => { + if (modeldata?.checkInTime && !modeldata?.checkOutTime) { + const checkIn = new Date(modeldata.checkInTime); + const [time, modifier] = data.markTime.split(" "); + const [hourStr, minuteStr] = time.split(":"); + let hour = parseInt(hourStr, 10); + const minute = parseInt(minuteStr, 10); - if (modifier === "PM" && hour !== 12) hour += 12; - if (modifier === "AM" && hour === 12) hour = 0; + if (modifier === "PM" && hour !== 12) hour += 12; + if (modifier === "AM" && hour === 12) hour = 0; - const checkOut = new Date(checkIn); - checkOut.setHours(hour, minute, 0, 0); + const checkOut = new Date(checkIn); + checkOut.setHours(hour, minute, 0, 0); - return checkOut >= checkIn; + return checkOut >= checkIn; + } + return true; + }, + { + message: "Checkout time must be later than check-in time", + path: ["markTime"], } - return true; - }, { - message: "Checkout time must be later than check-in time", - path: ["markTime"], - }); + ); }; const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => { + const [currentProject, setCurrentProject] = useState(null); const projectId = useSelectedProject(); + const { projectNames, loading } = useProjectName(); const { mutate: MarkAttendance } = useMarkAttendance(); const [isLoading, setIsLoading] = useState(false); const coords = usePositionTracker(); @@ -95,17 +101,24 @@ const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => { closeModal(); }; + useEffect(() => { + if (projectId && projectNames) { + setCurrentProject( + projectNames?.find((project) => project.id === projectId) + ); + } + }, [projectNames, projectId, loading]); + return (
-
diff --git a/src/components/Expenses/ExpenseSchema.js b/src/components/Expenses/ExpenseSchema.js index b1339228..786aa5b9 100644 --- a/src/components/Expenses/ExpenseSchema.js +++ b/src/components/Expenses/ExpenseSchema.js @@ -1,4 +1,5 @@ import { z } from "zod"; +import { localToUtc } from "../../utils/appUtils"; const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB const ALLOWED_TYPES = [ @@ -17,15 +18,12 @@ export const ExpenseSchema = (expenseTypes) => { .min(1, { message: "Expense type is required" }), paymentModeId: z.string().min(1, { message: "Payment mode is required" }), paidById: z.string().min(1, { message: "Employee name is required" }), - transactionDate: z - .string() - .min(1, { message: "Date is required" }) - , + transactionDate: z.string().min(1, { message: "Date is required" }), transactionId: z.string().optional(), description: z.string().min(1, { message: "Description is required" }), location: z.string().min(1, { message: "Location is required" }), supplerName: z.string().min(1, { message: "Supplier name is required" }), - gstNumber :z.string().optional(), + gstNumber: z.string().optional(), amount: z.coerce .number({ invalid_type_error: "Amount is required and must be a number", @@ -54,8 +52,6 @@ export const ExpenseSchema = (expenseTypes) => { }) ) .nonempty({ message: "At least one file attachment is required" }), - - }) .refine( (data) => { @@ -68,9 +64,14 @@ export const ExpenseSchema = (expenseTypes) => { path: ["paidById"], } ) - .superRefine((data, ctx) => { - const expenseType = expenseTypes.find((et) => et.id === data.expensesTypeId); - if (expenseType?.noOfPersonsRequired && (!data.noOfPersons || data.noOfPersons < 1)) { + .superRefine((data, ctx) => { + const expenseType = expenseTypes.find( + (et) => et.id === data.expensesTypeId + ); + if ( + expenseType?.noOfPersonsRequired && + (!data.noOfPersons || data.noOfPersons < 1) + ) { ctx.addIssue({ code: z.ZodIssueCode.custom, message: "No. of Persons is required and must be at least 1", @@ -92,12 +93,14 @@ export const defaultExpense = { supplerName: "", amount: "", noOfPersons: "", - gstNumber:"", + gstNumber: "", billAttachments: [], }; - -export const ExpenseActionScheam = (isReimbursement = false) => { +export const ExpenseActionScheam = ( + isReimbursement = false, + transactionDate +) => { return z .object({ comment: z.string().min(1, { message: "Please leave comment" }), @@ -122,6 +125,15 @@ export const ExpenseActionScheam = (isReimbursement = false) => { message: "Reimburse Date is required", }); } + // let reimburse_Date = localToUtc(data.reimburseDate); + // if (transactionDate > reimburse_Date) { + // ctx.addIssue({ + // code: z.ZodIssueCode.custom, + // path: ["reimburseDate"], + // message: + // "Reimburse Date must be greater than or equal to Expense created Date", + // }); + // } if (!data.reimburseById) { ctx.addIssue({ code: z.ZodIssueCode.custom, @@ -133,7 +145,7 @@ export const ExpenseActionScheam = (isReimbursement = false) => { }); }; - export const defaultActionValues = { +export const defaultActionValues = { comment: "", statusId: "", @@ -142,8 +154,6 @@ export const ExpenseActionScheam = (isReimbursement = false) => { reimburseById: null, }; - - export const SearchSchema = z.object({ projectIds: z.array(z.string()).optional(), statusIds: z.array(z.string()).optional(), @@ -163,4 +173,3 @@ export const defaultFilter = { startDate: null, endDate: null, }; - diff --git a/src/components/Expenses/ViewExpense.jsx b/src/components/Expenses/ViewExpense.jsx index 8fca0998..7db45afc 100644 --- a/src/components/Expenses/ViewExpense.jsx +++ b/src/components/Expenses/ViewExpense.jsx @@ -9,7 +9,7 @@ import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { defaultActionValues, ExpenseActionScheam } from "./ExpenseSchema"; import { useExpenseContext } from "../../pages/Expense/ExpensePage"; -import { getColorNameFromHex, getIconByFileType } from "../../utils/appUtils"; +import { getColorNameFromHex, getIconByFileType, localToUtc } from "../../utils/appUtils"; import { ExpenseDetailsSkeleton } from "./ExpenseSkeleton"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { @@ -38,7 +38,7 @@ const ViewExpense = ({ ExpenseId }) => { const IsReview = useHasUserPermission(REVIEW_EXPENSE); const [imageLoaded, setImageLoaded] = useState({}); const { setDocumentView } = useExpenseContext(); - const ActionSchema = ExpenseActionScheam(IsPaymentProcess) ?? z.object({}); + const ActionSchema = ExpenseActionScheam(IsPaymentProcess,data?.createdAt) ?? z.object({}); const navigate = useNavigate(); const { register, @@ -91,9 +91,7 @@ const ViewExpense = ({ ExpenseId }) => { const onSubmit = (formData) => { const Payload = { ...formData, - reimburseDate: moment - .utc(formData.reimburseDate, "DD-MM-YYYY") - .toISOString(), + reimburseDate:localToUtc(formData.reimburseDate), expenseId: ExpenseId, comment: formData.comment, }; @@ -397,7 +395,8 @@ const ViewExpense = ({ ExpenseId }) => { {errors.reimburseDate && ( @@ -410,7 +409,7 @@ const ViewExpense = ({ ExpenseId }) => { diff --git a/src/components/Layout/Header.jsx b/src/components/Layout/Header.jsx index 1aa13c91..ec55b48c 100644 --- a/src/components/Layout/Header.jsx +++ b/src/components/Layout/Header.jsx @@ -1,3 +1,4 @@ +import { useCallback, useEffect, useState,useMemo } from "react"; import getGreetingMessage from "../../utils/greetingHandler"; import { cacheData, @@ -14,119 +15,103 @@ import { useLocation, useNavigate, useParams } from "react-router-dom"; import Avatar from "../../components/common/Avatar"; import { useChangePassword } from "../Context/ChangePasswordContext"; import { useProjectModal, useProjects } from "../../hooks/useProjects"; -import { useCallback, useEffect, useState } from "react"; import { useProjectName } from "../../hooks/useProjects"; import eventBus from "../../services/eventBus"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; -import { MANAGE_PROJECT } from "../../utils/constants"; +import { ALLOW_PROJECTSTATUS_ID, MANAGE_PROJECT, UUID_REGEX } from "../../utils/constants"; import { useAuthModal, useLogout } from "../../hooks/useAuth"; const Header = () => { - const { profile } = useProfile(); + const { profile } = useProfile(); + const { data: masterData } = useMaster(); const location = useLocation(); const dispatch = useDispatch(); - const { data, loading } = useMaster(); const navigate = useNavigate(); - const { onOpen } = useAuthModal(); - const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT); - const { mutate: logout, isPending: logouting } = useLogout(); + const { openModal } = useProjectModal(); + const { mutate: logout, isPending: logouting } = useLogout(); + const { onOpen } = useAuthModal(); + const { openChangePassword } = useChangePassword(); + const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT); - const isDashboardPath = - /^\/dashboard$/.test(location.pathname) || /^\/$/.test(location.pathname); - const isProjectPath = /^\/projects$/.test(location.pathname); + const pathname = location.pathname; - const showProjectDropdown = (pathname) => { - const isDirectoryPath = /^\/directory$/.test(pathname); + // ======= MEMO CHECKS ======= + const isDashboardPath = pathname === "/" || pathname === "/dashboard"; + const isProjectPath = pathname === "/projects"; + const isDirectory = pathname === "/directory"; + const isEmployeeList = pathname === "/employees"; + const isExpense = pathname === "/expenses"; + const isEmployeeProfile = UUID_REGEX.test(pathname); - // const isProfilePage = /^\/employee$/.test(location.pathname); - const isProfilePage = - /^\/employee\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test( - pathname - ); - const isExpensePage = /^\/expenses$/.test(pathname); - const isEmployeePage = /^\/employees$/.test(pathname) + const hideDropPaths = + isDirectory || isEmployeeList || isExpense || isEmployeeProfile; - return !(isDirectoryPath || isProfilePage || isExpensePage || isEmployeePage); - }; - const allowedProjectStatusIds = [ - "603e994b-a27f-4e5d-a251-f3d69b0498ba", - "cdad86aa-8a56-4ff4-b633-9c629057dfef", - "b74da4c2-d07e-46f2-9919-e75e49b12731", - ]; - - const getRole = (roles, joRoleId) => { - if (!Array.isArray(roles)) return "User"; - let role = roles.find((role) => role.id === joRoleId); - return role ? role.name : "User"; - }; - - const handleProfilePage = () => { - navigate(`/employee/${profile?.employeeInfo?.id}`); - }; + const showProjectDropdown = !hideDropPaths; + // ===== Project Names & Selected Project ===== const { projectNames, loading: projectLoading, fetchData } = useProjectName(); - const selectedProject = useSelectedProject(); - const projectsForDropdown = isDashboardPath - ? projectNames - : projectNames?.filter((project) => - allowedProjectStatusIds.includes(project.projectStatusId) - ); + const projectsForDropdown = useMemo( + () => + isDashboardPath + ? projectNames + : projectNames?.filter((project) => + ALLOW_PROJECTSTATUS_ID.includes(project.projectStatusId) + ), + [projectNames, isDashboardPath] + ); - let currentProjectDisplayName; - if (projectLoading) { - currentProjectDisplayName = "Loading..."; - } else if (!projectNames || projectNames.length === 0) { - currentProjectDisplayName = "No Projects Assigned"; - } else if (projectNames.length === 1) { - currentProjectDisplayName = projectNames[0].name; - } else { - if (selectedProject === null) { - currentProjectDisplayName = projectNames[0].name; - } else { - const selectedProjectObj = projectNames.find( - (p) => p?.id === selectedProject - ); - currentProjectDisplayName = selectedProjectObj - ? selectedProjectObj.name - : "All Projects"; - } - } + const currentProjectDisplayName = useMemo(() => { + if (projectLoading) return "Loading..."; + if (!projectNames?.length) return "No Projects Assigned"; + if (projectNames.length === 1) return projectNames[0].name; - const { openChangePassword } = useChangePassword(); + const selectedObj = projectNames.find((p) => p.id === selectedProject); + return selectedObj + ? selectedObj.name + : projectNames[0]?.name || "No Projects Assigned"; + }, [projectLoading, projectNames, selectedProject]); + // ===== Role Helper ===== + const getRole = (roles, joRoleId) => { + if (!Array.isArray(roles)) return "User"; + return roles.find((r) => r.id === joRoleId)?.name || "User"; + }; + + // ===== Navigate to Profile ===== + const handleProfilePage = () => + navigate(`/employee/${profile?.employeeInfo?.id}`); + + // ===== Set default project on load ===== useEffect(() => { if ( - projectNames && - projectNames.length > 0 && + projectNames?.length && selectedProject === undefined && !getCachedData("hasReceived") ) { if (projectNames.length === 1) { - dispatch(setProjectId(projectNames[0]?.id || null)); + dispatch(setProjectId(projectNames[0].id || null)); } else { if (isDashboardPath) { dispatch(setProjectId(null)); } else { - const firstAllowedProject = projectNames.find((project) => - allowedProjectStatusIds.includes(project.projectStatusId) + const firstAllowed = projectNames.find((project) => + ALLOW_PROJECTSTATUS_ID.includes(project.projectStatusId) ); - dispatch(setProjectId(firstAllowedProject?.id || null)); + dispatch(setProjectId(firstAllowed?.id || null)); } } } }, [projectNames, selectedProject, dispatch, isDashboardPath]); + // ===== Event Handlers ===== const handler = useCallback( async (data) => { if (!HasManageProjectPermission) { await fetchData(); - const projectExist = data.projectIds.some( - (item) => item === selectedProject - ); - if (projectExist) { + if (data.projectIds?.includes(selectedProject)) { cacheData("hasReceived", false); } } @@ -136,14 +121,15 @@ const Header = () => { const newProjectHandler = useCallback( async (msg) => { - if ( msg.keyword === "Create_Project") { - await fetchData(); - } else if (projectNames?.some((item) => item.id === msg.response.id)) { + if ( + msg.keyword === "Create_Project" || + projectNames?.some((p) => p.id === msg.response?.id) + ) { await fetchData(); + cacheData("hasReceived", false); } - cacheData("hasReceived", false); }, - [ projectNames, fetchData] + [projectNames, fetchData] ); useEffect(() => { @@ -160,10 +146,10 @@ const Header = () => { }; }, [handler, newProjectHandler]); - const handleProjectChange = (project) => { - dispatch(setProjectId(project)); - - if (isProjectPath && project !== null) { + // ===== Project Change ===== + const handleProjectChange = (projectId) => { + dispatch(setProjectId(projectId)); + if (isProjectPath && projectId !== null) { navigate("/projects/details"); } }; @@ -187,7 +173,7 @@ const Header = () => { className="navbar-nav-right d-flex align-items-center justify-content-between" id="navbar-collapse" > - {showProjectDropdown(location.pathname) && ( + {showProjectDropdown && (
@@ -213,16 +199,6 @@ const Header = () => { className="dropdown-menu" style={{ overflow: "auto", maxHeight: "300px" }} > - {isProjectPath && ( -
  • - -
  • - )} {[...projectsForDropdown] .sort((a, b) => a?.name?.localeCompare(b.name)) .map((project) => ( @@ -290,7 +266,7 @@ const Header = () => { {profile?.employeeInfo?.firstName} - {getRole(data, profile?.employeeInfo?.joRoleId)} + {getRole(masterData, profile?.employeeInfo?.joRoleId)}
    diff --git a/src/pages/Directory/ContactsPage.jsx b/src/pages/Directory/ContactsPage.jsx index 01023229..54e2bdf5 100644 --- a/src/pages/Directory/ContactsPage.jsx +++ b/src/pages/Directory/ContactsPage.jsx @@ -86,6 +86,11 @@ const ContactsPage = ({ projectId, searchText, onExport }) => { )} + + + {data?.data?.length === 0 && (
    + {searchText ? `No contact found for "${searchText}"`:"No contacts found" } +
    )} {data?.data?.map((contact) => (
    { if (bytes < 1024) return bytes + " B"; else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB"; @@ -72,12 +72,11 @@ export const normalizeAllowedContentTypes = (allowedContentType) => { export function localToUtc(localDateString) { if (!localDateString || typeof localDateString !== "string") return null; + const [year, month, day] = localDateString.trim().split("-"); - if (!year || !month || !day) return null; - - const date = new Date(Number(year), Number(month) - 1, Number(day), 0, 0, 0); + const date = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day), 0, 0, 0)); return isNaN(date.getTime()) ? null : date.toISOString(); -} +} \ No newline at end of file diff --git a/src/utils/constants.jsx b/src/utils/constants.jsx index 62c686c9..8ea2da02 100644 --- a/src/utils/constants.jsx +++ b/src/utils/constants.jsx @@ -1,3 +1,8 @@ +export const BASE_URL = process.env.VITE_BASE_URL; + +// export const BASE_URL = "https://api.marcoaiot.com"; + + export const THRESH_HOLD = 48; // hours export const DURATION_TIME = 10; // minutes export const ITEMS_PER_PAGE = 20; @@ -140,8 +145,17 @@ export const PROJECT_STATUS = [ label: "Completed", }, ]; + + +export const UUID_REGEX = + /^\/employee\/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/; + +export const ALLOW_PROJECTSTATUS_ID = [ + "603e994b-a27f-4e5d-a251-f3d69b0498ba", + "cdad86aa-8a56-4ff4-b633-9c629057dfef", + "b74da4c2-d07e-46f2-9919-e75e49b12731", +]; + export const DEFAULT_EMPTY_STATUS_ID = "00000000-0000-0000-0000-000000000000"; -export const BASE_URL = process.env.VITE_BASE_URL; -// export const BASE_URL = "https://api.marcoaiot.com";