fixed expense date, added project name at check in checkout modal
This commit is contained in:
parent
2aae7194b7
commit
49b597c833
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@ -9,6 +9,7 @@ import showToast from "../../services/toastService";
|
|||||||
import { checkIfCurrentDate } from "../../utils/dateUtils";
|
import { checkIfCurrentDate } from "../../utils/dateUtils";
|
||||||
import { useMarkAttendance } from "../../hooks/useAttendance";
|
import { useMarkAttendance } from "../../hooks/useAttendance";
|
||||||
import { useSelectedProject } from "../../slices/apiDataManager";
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
|
|
||||||
const createSchema = (modeldata) => {
|
const createSchema = (modeldata) => {
|
||||||
return z
|
return z
|
||||||
@ -19,7 +20,8 @@ const createSchema = (modeldata) => {
|
|||||||
.max(200, "Description should be less than 200 characters")
|
.max(200, "Description should be less than 200 characters")
|
||||||
.optional(),
|
.optional(),
|
||||||
})
|
})
|
||||||
.refine((data) => {
|
.refine(
|
||||||
|
(data) => {
|
||||||
if (modeldata?.checkInTime && !modeldata?.checkOutTime) {
|
if (modeldata?.checkInTime && !modeldata?.checkOutTime) {
|
||||||
const checkIn = new Date(modeldata.checkInTime);
|
const checkIn = new Date(modeldata.checkInTime);
|
||||||
const [time, modifier] = data.markTime.split(" ");
|
const [time, modifier] = data.markTime.split(" ");
|
||||||
@ -36,14 +38,18 @@ const createSchema = (modeldata) => {
|
|||||||
return checkOut >= checkIn;
|
return checkOut >= checkIn;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
message: "Checkout time must be later than check-in time",
|
message: "Checkout time must be later than check-in time",
|
||||||
path: ["markTime"],
|
path: ["markTime"],
|
||||||
});
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => {
|
const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => {
|
||||||
|
const [currentProject, setCurrentProject] = useState(null);
|
||||||
const projectId = useSelectedProject();
|
const projectId = useSelectedProject();
|
||||||
|
const { projectNames, loading } = useProjectName();
|
||||||
const { mutate: MarkAttendance } = useMarkAttendance();
|
const { mutate: MarkAttendance } = useMarkAttendance();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const coords = usePositionTracker();
|
const coords = usePositionTracker();
|
||||||
@ -95,17 +101,24 @@ const CheckInCheckOut = ({ modeldata, closeModal, handleSubmitForm }) => {
|
|||||||
closeModal();
|
closeModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (projectId && projectNames) {
|
||||||
|
setCurrentProject(
|
||||||
|
projectNames?.find((project) => project.id === projectId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [projectNames, projectId, loading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
<form className="row g-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="col-12 d-flex justify-content-center">
|
<div className="col-12 d-flex justify-content-center">
|
||||||
<label className="fs-5 text-dark text-center">
|
<label className="fs-5 text-dark text-center">
|
||||||
{modeldata?.checkInTime && !modeldata?.checkOutTime
|
{modeldata?.checkInTime && !modeldata?.checkOutTime
|
||||||
? "Check-out :"
|
? `Check out for ${currentProject?.name}`
|
||||||
: "Check-in :"}
|
: `Check In for ${currentProject?.name}`}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className="col-6 col-md-6 text-start">
|
<div className="col-6 col-md-6 text-start">
|
||||||
<label className="form-label" htmlFor="checkInDate">
|
<label className="form-label" htmlFor="checkInDate">
|
||||||
{modeldata?.checkInTime && !modeldata?.checkOutTime
|
{modeldata?.checkInTime && !modeldata?.checkOutTime
|
||||||
|
@ -13,11 +13,10 @@ const formatDate = (dateStr) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const AttendanceOverview = () => {
|
const AttendanceOverview = ({projectId}) => {
|
||||||
const [dayRange, setDayRange] = useState(7);
|
const [dayRange, setDayRange] = useState(7);
|
||||||
const [view, setView] = useState("chart");
|
const [view, setView] = useState("chart");
|
||||||
|
|
||||||
const projectId = useSelector((store) => store.localVariables.projectId);
|
|
||||||
const { attendanceOverviewData, loading, error } = useAttendanceOverviewData(
|
const { attendanceOverviewData, loading, error } = useAttendanceOverviewData(
|
||||||
projectId,
|
projectId,
|
||||||
dayRange
|
dayRange
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
// import {
|
// import {
|
||||||
// useDashboardProjectsCardData,
|
// useDashboardProjectsCardData,
|
||||||
// useDashboardTeamsCardData,
|
// useDashboardTeamsCardData,
|
||||||
@ -14,25 +14,35 @@ import { useSelector } from "react-redux";
|
|||||||
// import ProjectProgressChart from "./ProjectProgressChart";
|
// import ProjectProgressChart from "./ProjectProgressChart";
|
||||||
// import ProjectOverview from "../Project/ProjectOverview";
|
// import ProjectOverview from "../Project/ProjectOverview";
|
||||||
import AttendanceOverview from "./AttendanceChart";
|
import AttendanceOverview from "./AttendanceChart";
|
||||||
|
import { useSelectedProject } from "../../slices/apiDataManager";
|
||||||
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
|
const { projectNames, loading: projectLoading } = useProjectName();
|
||||||
|
const selectedProject = useSelectedProject();
|
||||||
|
const dispatch = useDispatch();
|
||||||
// const { projectsCardData } = useDashboardProjectsCardData();
|
// const { projectsCardData } = useDashboardProjectsCardData();
|
||||||
// const { teamsCardData } = useDashboardTeamsCardData();
|
// const { teamsCardData } = useDashboardTeamsCardData();
|
||||||
// const { tasksCardData } = useDashboardTasksCardData();
|
// const { tasksCardData } = useDashboardTasksCardData();
|
||||||
|
|
||||||
// Get the selected project ID from Redux store
|
// Get the selected project ID from Redux store
|
||||||
const projectId = useSelector((store) => store.localVariables.projectId);
|
|
||||||
const isAllProjectsSelected = projectId === null;
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!projectLoading && projectNames.length === 1 && !selectedProject) {
|
||||||
|
dispatch(setProjectId(projectNames[0].id));
|
||||||
|
}
|
||||||
|
}, [projectNames, projectLoading, selectedProject, dispatch]);
|
||||||
|
console.log(projectNames)
|
||||||
|
// Show attendance if project selected or single project exists
|
||||||
|
const shouldShowAttendance =
|
||||||
|
!projectLoading && (selectedProject || projectNames.length === 1);
|
||||||
return (
|
return (
|
||||||
<div className="container-fluid mt-5">
|
<div className="container-fluid mt-5">
|
||||||
<div className="row gy-4">
|
<div className="row gy-4">
|
||||||
|
{/* {shouldShowAttendance && ( */}
|
||||||
{!isAllProjectsSelected && (
|
|
||||||
<div className="col-xxl-6 col-lg-6">
|
<div className="col-xxl-6 col-lg-6">
|
||||||
<AttendanceOverview /> {/* ✅ Removed unnecessary projectId prop */}
|
<AttendanceOverview projectId={selectedProject} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -117,6 +117,7 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
|||||||
endField="endDate"
|
endField="endDate"
|
||||||
resetSignal={resetKey}
|
resetSignal={resetKey}
|
||||||
defaultRange={false}
|
defaultRange={false}
|
||||||
|
maxDate={new Date()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import { localToUtc } from "../../utils/appUtils";
|
||||||
|
|
||||||
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
|
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
|
||||||
const ALLOWED_TYPES = [
|
const ALLOWED_TYPES = [
|
||||||
@ -17,15 +18,12 @@ export const ExpenseSchema = (expenseTypes) => {
|
|||||||
.min(1, { message: "Expense type is required" }),
|
.min(1, { message: "Expense type is required" }),
|
||||||
paymentModeId: z.string().min(1, { message: "Payment mode is required" }),
|
paymentModeId: z.string().min(1, { message: "Payment mode is required" }),
|
||||||
paidById: z.string().min(1, { message: "Employee name is required" }),
|
paidById: z.string().min(1, { message: "Employee name is required" }),
|
||||||
transactionDate: z
|
transactionDate: z.string().min(1, { message: "Date is required" }),
|
||||||
.string()
|
|
||||||
.min(1, { message: "Date is required" })
|
|
||||||
,
|
|
||||||
transactionId: z.string().optional(),
|
transactionId: z.string().optional(),
|
||||||
description: z.string().min(1, { message: "Description is required" }),
|
description: z.string().min(1, { message: "Description is required" }),
|
||||||
location: z.string().min(1, { message: "Location is required" }),
|
location: z.string().min(1, { message: "Location is required" }),
|
||||||
supplerName: z.string().min(1, { message: "Supplier name is required" }),
|
supplerName: z.string().min(1, { message: "Supplier name is required" }),
|
||||||
gstNumber :z.string().optional(),
|
gstNumber: z.string().optional(),
|
||||||
amount: z.coerce
|
amount: z.coerce
|
||||||
.number({
|
.number({
|
||||||
invalid_type_error: "Amount is required and must be a 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" }),
|
.nonempty({ message: "At least one file attachment is required" }),
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
@ -69,8 +65,13 @@ export const ExpenseSchema = (expenseTypes) => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
const expenseType = expenseTypes.find((et) => et.id === data.expensesTypeId);
|
const expenseType = expenseTypes.find(
|
||||||
if (expenseType?.noOfPersonsRequired && (!data.noOfPersons || data.noOfPersons < 1)) {
|
(et) => et.id === data.expensesTypeId
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
expenseType?.noOfPersonsRequired &&
|
||||||
|
(!data.noOfPersons || data.noOfPersons < 1)
|
||||||
|
) {
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
code: z.ZodIssueCode.custom,
|
code: z.ZodIssueCode.custom,
|
||||||
message: "No. of Persons is required and must be at least 1",
|
message: "No. of Persons is required and must be at least 1",
|
||||||
@ -92,12 +93,14 @@ export const defaultExpense = {
|
|||||||
supplerName: "",
|
supplerName: "",
|
||||||
amount: "",
|
amount: "",
|
||||||
noOfPersons: "",
|
noOfPersons: "",
|
||||||
gstNumber:"",
|
gstNumber: "",
|
||||||
billAttachments: [],
|
billAttachments: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const ExpenseActionScheam = (
|
||||||
export const ExpenseActionScheam = (isReimbursement = false) => {
|
isReimbursement = false,
|
||||||
|
transactionDate
|
||||||
|
) => {
|
||||||
return z
|
return z
|
||||||
.object({
|
.object({
|
||||||
comment: z.string().min(1, { message: "Please leave comment" }),
|
comment: z.string().min(1, { message: "Please leave comment" }),
|
||||||
@ -122,6 +125,15 @@ export const ExpenseActionScheam = (isReimbursement = false) => {
|
|||||||
message: "Reimburse Date is required",
|
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) {
|
if (!data.reimburseById) {
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
code: z.ZodIssueCode.custom,
|
code: z.ZodIssueCode.custom,
|
||||||
@ -133,7 +145,7 @@ export const ExpenseActionScheam = (isReimbursement = false) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultActionValues = {
|
export const defaultActionValues = {
|
||||||
comment: "",
|
comment: "",
|
||||||
statusId: "",
|
statusId: "",
|
||||||
|
|
||||||
@ -142,8 +154,6 @@ export const ExpenseActionScheam = (isReimbursement = false) => {
|
|||||||
reimburseById: null,
|
reimburseById: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const SearchSchema = z.object({
|
export const SearchSchema = z.object({
|
||||||
projectIds: z.array(z.string()).optional(),
|
projectIds: z.array(z.string()).optional(),
|
||||||
statusIds: z.array(z.string()).optional(),
|
statusIds: z.array(z.string()).optional(),
|
||||||
@ -163,4 +173,3 @@ export const defaultFilter = {
|
|||||||
startDate: null,
|
startDate: null,
|
||||||
endDate: null,
|
endDate: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ import { useForm } from "react-hook-form";
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { defaultActionValues, ExpenseActionScheam } from "./ExpenseSchema";
|
import { defaultActionValues, ExpenseActionScheam } from "./ExpenseSchema";
|
||||||
import { useExpenseContext } from "../../pages/Expense/ExpensePage";
|
import { useExpenseContext } from "../../pages/Expense/ExpensePage";
|
||||||
import { getColorNameFromHex, getIconByFileType } from "../../utils/appUtils";
|
import { getColorNameFromHex, getIconByFileType, localToUtc } from "../../utils/appUtils";
|
||||||
import { ExpenseDetailsSkeleton } from "./ExpenseSkeleton";
|
import { ExpenseDetailsSkeleton } from "./ExpenseSkeleton";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import {
|
import {
|
||||||
@ -38,7 +38,7 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
const IsReview = useHasUserPermission(REVIEW_EXPENSE);
|
const IsReview = useHasUserPermission(REVIEW_EXPENSE);
|
||||||
const [imageLoaded, setImageLoaded] = useState({});
|
const [imageLoaded, setImageLoaded] = useState({});
|
||||||
const { setDocumentView } = useExpenseContext();
|
const { setDocumentView } = useExpenseContext();
|
||||||
const ActionSchema = ExpenseActionScheam(IsPaymentProcess) ?? z.object({});
|
const ActionSchema = ExpenseActionScheam(IsPaymentProcess,data?.createdAt) ?? z.object({});
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
@ -91,9 +91,7 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
const onSubmit = (formData) => {
|
const onSubmit = (formData) => {
|
||||||
const Payload = {
|
const Payload = {
|
||||||
...formData,
|
...formData,
|
||||||
reimburseDate: moment
|
reimburseDate:localToUtc(formData.reimburseDate),
|
||||||
.utc(formData.reimburseDate, "DD-MM-YYYY")
|
|
||||||
.toISOString(),
|
|
||||||
expenseId: ExpenseId,
|
expenseId: ExpenseId,
|
||||||
comment: formData.comment,
|
comment: formData.comment,
|
||||||
};
|
};
|
||||||
@ -397,7 +395,8 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
<DatePicker
|
<DatePicker
|
||||||
name="reimburseDate"
|
name="reimburseDate"
|
||||||
control={control}
|
control={control}
|
||||||
minDate={data?.transactionDate}
|
minDate={data?.createdAt}
|
||||||
|
maxDate={new Date()}
|
||||||
/>
|
/>
|
||||||
{errors.reimburseDate && (
|
{errors.reimburseDate && (
|
||||||
<small className="danger-text">
|
<small className="danger-text">
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { useCallback, useEffect, useState,useMemo } from "react";
|
||||||
import getGreetingMessage from "../../utils/greetingHandler";
|
import getGreetingMessage from "../../utils/greetingHandler";
|
||||||
import {
|
import {
|
||||||
cacheData,
|
cacheData,
|
||||||
@ -14,119 +15,103 @@ import { useLocation, useNavigate, useParams } from "react-router-dom";
|
|||||||
import Avatar from "../../components/common/Avatar";
|
import Avatar from "../../components/common/Avatar";
|
||||||
import { useChangePassword } from "../Context/ChangePasswordContext";
|
import { useChangePassword } from "../Context/ChangePasswordContext";
|
||||||
import { useProjectModal, useProjects } from "../../hooks/useProjects";
|
import { useProjectModal, useProjects } from "../../hooks/useProjects";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
|
||||||
import { useProjectName } from "../../hooks/useProjects";
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
import eventBus from "../../services/eventBus";
|
import eventBus from "../../services/eventBus";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
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";
|
import { useAuthModal, useLogout } from "../../hooks/useAuth";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { profile } = useProfile();
|
const { profile } = useProfile();
|
||||||
|
const { data: masterData } = useMaster();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { data, loading } = useMaster();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { onOpen } = useAuthModal();
|
|
||||||
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
|
|
||||||
const { mutate: logout, isPending: logouting } = useLogout();
|
|
||||||
const { openModal } = useProjectModal();
|
const { openModal } = useProjectModal();
|
||||||
|
const { mutate: logout, isPending: logouting } = useLogout();
|
||||||
|
const { onOpen } = useAuthModal();
|
||||||
|
const { openChangePassword } = useChangePassword();
|
||||||
|
const HasManageProjectPermission = useHasUserPermission(MANAGE_PROJECT);
|
||||||
|
|
||||||
const isDashboardPath =
|
const pathname = location.pathname;
|
||||||
/^\/dashboard$/.test(location.pathname) || /^\/$/.test(location.pathname);
|
|
||||||
const isProjectPath = /^\/projects$/.test(location.pathname);
|
|
||||||
|
|
||||||
const showProjectDropdown = (pathname) => {
|
// ======= MEMO CHECKS =======
|
||||||
const isDirectoryPath = /^\/directory$/.test(pathname);
|
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 hideDropPaths =
|
||||||
const isProfilePage =
|
isDirectory || isEmployeeList || isExpense || isEmployeeProfile;
|
||||||
/^\/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)
|
|
||||||
|
|
||||||
return !(isDirectoryPath || isProfilePage || isExpensePage || isEmployeePage);
|
const showProjectDropdown = !hideDropPaths;
|
||||||
};
|
|
||||||
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}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// ===== Project Names & Selected Project =====
|
||||||
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
const { projectNames, loading: projectLoading, fetchData } = useProjectName();
|
||||||
|
|
||||||
const selectedProject = useSelectedProject();
|
const selectedProject = useSelectedProject();
|
||||||
|
|
||||||
const projectsForDropdown = isDashboardPath
|
const projectsForDropdown = useMemo(
|
||||||
|
() =>
|
||||||
|
isDashboardPath
|
||||||
? projectNames
|
? projectNames
|
||||||
: projectNames?.filter((project) =>
|
: projectNames?.filter((project) =>
|
||||||
allowedProjectStatusIds.includes(project.projectStatusId)
|
ALLOW_PROJECTSTATUS_ID.includes(project.projectStatusId)
|
||||||
|
),
|
||||||
|
[projectNames, isDashboardPath]
|
||||||
);
|
);
|
||||||
|
|
||||||
let currentProjectDisplayName;
|
const currentProjectDisplayName = useMemo(() => {
|
||||||
if (projectLoading) {
|
if (projectLoading) return "Loading...";
|
||||||
currentProjectDisplayName = "Loading...";
|
if (!projectNames?.length) return "No Projects Assigned";
|
||||||
} else if (!projectNames || projectNames.length === 0) {
|
if (projectNames.length === 1) return projectNames[0].name;
|
||||||
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 { 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(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
projectNames &&
|
projectNames?.length &&
|
||||||
projectNames.length > 0 &&
|
|
||||||
selectedProject === undefined &&
|
selectedProject === undefined &&
|
||||||
!getCachedData("hasReceived")
|
!getCachedData("hasReceived")
|
||||||
) {
|
) {
|
||||||
if (projectNames.length === 1) {
|
if (projectNames.length === 1) {
|
||||||
dispatch(setProjectId(projectNames[0]?.id || null));
|
dispatch(setProjectId(projectNames[0].id || null));
|
||||||
} else {
|
} else {
|
||||||
if (isDashboardPath) {
|
if (isDashboardPath) {
|
||||||
dispatch(setProjectId(null));
|
dispatch(setProjectId(null));
|
||||||
} else {
|
} else {
|
||||||
const firstAllowedProject = projectNames.find((project) =>
|
const firstAllowed = projectNames.find((project) =>
|
||||||
allowedProjectStatusIds.includes(project.projectStatusId)
|
ALLOW_PROJECTSTATUS_ID.includes(project.projectStatusId)
|
||||||
);
|
);
|
||||||
dispatch(setProjectId(firstAllowedProject?.id || null));
|
dispatch(setProjectId(firstAllowed?.id || null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [projectNames, selectedProject, dispatch, isDashboardPath]);
|
}, [projectNames, selectedProject, dispatch, isDashboardPath]);
|
||||||
|
|
||||||
|
// ===== Event Handlers =====
|
||||||
const handler = useCallback(
|
const handler = useCallback(
|
||||||
async (data) => {
|
async (data) => {
|
||||||
if (!HasManageProjectPermission) {
|
if (!HasManageProjectPermission) {
|
||||||
await fetchData();
|
await fetchData();
|
||||||
const projectExist = data.projectIds.some(
|
if (data.projectIds?.includes(selectedProject)) {
|
||||||
(item) => item === selectedProject
|
|
||||||
);
|
|
||||||
if (projectExist) {
|
|
||||||
cacheData("hasReceived", false);
|
cacheData("hasReceived", false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -136,14 +121,15 @@ const Header = () => {
|
|||||||
|
|
||||||
const newProjectHandler = useCallback(
|
const newProjectHandler = useCallback(
|
||||||
async (msg) => {
|
async (msg) => {
|
||||||
if ( msg.keyword === "Create_Project") {
|
if (
|
||||||
|
msg.keyword === "Create_Project" ||
|
||||||
|
projectNames?.some((p) => p.id === msg.response?.id)
|
||||||
|
) {
|
||||||
await fetchData();
|
await fetchData();
|
||||||
} else if (projectNames?.some((item) => item.id === msg.response.id)) {
|
|
||||||
await fetchData();
|
|
||||||
}
|
|
||||||
cacheData("hasReceived", false);
|
cacheData("hasReceived", false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[ projectNames, fetchData]
|
[projectNames, fetchData]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -160,10 +146,10 @@ const Header = () => {
|
|||||||
};
|
};
|
||||||
}, [handler, newProjectHandler]);
|
}, [handler, newProjectHandler]);
|
||||||
|
|
||||||
const handleProjectChange = (project) => {
|
// ===== Project Change =====
|
||||||
dispatch(setProjectId(project));
|
const handleProjectChange = (projectId) => {
|
||||||
|
dispatch(setProjectId(projectId));
|
||||||
if (isProjectPath && project !== null) {
|
if (isProjectPath && projectId !== null) {
|
||||||
navigate("/projects/details");
|
navigate("/projects/details");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -187,7 +173,7 @@ const Header = () => {
|
|||||||
className="navbar-nav-right d-flex align-items-center justify-content-between"
|
className="navbar-nav-right d-flex align-items-center justify-content-between"
|
||||||
id="navbar-collapse"
|
id="navbar-collapse"
|
||||||
>
|
>
|
||||||
{showProjectDropdown(location.pathname) && (
|
{showProjectDropdown && (
|
||||||
<div className="align-items-center">
|
<div className="align-items-center">
|
||||||
<i className="rounded-circle bx bx-building-house bx-sm-lg bx-md me-2"></i>
|
<i className="rounded-circle bx bx-building-house bx-sm-lg bx-md me-2"></i>
|
||||||
<div className="btn-group">
|
<div className="btn-group">
|
||||||
@ -213,16 +199,6 @@ const Header = () => {
|
|||||||
className="dropdown-menu"
|
className="dropdown-menu"
|
||||||
style={{ overflow: "auto", maxHeight: "300px" }}
|
style={{ overflow: "auto", maxHeight: "300px" }}
|
||||||
>
|
>
|
||||||
{isProjectPath && (
|
|
||||||
<li>
|
|
||||||
<button
|
|
||||||
className="dropdown-item"
|
|
||||||
onClick={() => handleProjectChange(null)}
|
|
||||||
>
|
|
||||||
All Projects
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
)}
|
|
||||||
{[...projectsForDropdown]
|
{[...projectsForDropdown]
|
||||||
.sort((a, b) => a?.name?.localeCompare(b.name))
|
.sort((a, b) => a?.name?.localeCompare(b.name))
|
||||||
.map((project) => (
|
.map((project) => (
|
||||||
@ -290,7 +266,7 @@ const Header = () => {
|
|||||||
{profile?.employeeInfo?.firstName}
|
{profile?.employeeInfo?.firstName}
|
||||||
</span>
|
</span>
|
||||||
<small className="text-muted">
|
<small className="text-muted">
|
||||||
{getRole(data, profile?.employeeInfo?.joRoleId)}
|
{getRole(masterData, profile?.employeeInfo?.joRoleId)}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -86,6 +86,11 @@ const ContactsPage = ({ projectId, searchText, onExport }) => {
|
|||||||
<Loader />
|
<Loader />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
{data?.data?.length === 0 && (<div className="py-12 text-secondary">
|
||||||
|
{searchText ? `No contact found for "${searchText}"`:"No contacts found" }
|
||||||
|
</div>)}
|
||||||
{data?.data?.map((contact) => (
|
{data?.data?.map((contact) => (
|
||||||
<div
|
<div
|
||||||
key={contact.id}
|
key={contact.id}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { format, parseISO } from "date-fns";
|
import { parseISO, formatISO } from "date-fns";
|
||||||
export const formatFileSize = (bytes) => {
|
export const formatFileSize = (bytes) => {
|
||||||
if (bytes < 1024) return bytes + " B";
|
if (bytes < 1024) return bytes + " B";
|
||||||
else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB";
|
else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + " KB";
|
||||||
@ -72,12 +72,11 @@ export const normalizeAllowedContentTypes = (allowedContentType) => {
|
|||||||
export function localToUtc(localDateString) {
|
export function localToUtc(localDateString) {
|
||||||
if (!localDateString || typeof localDateString !== "string") return null;
|
if (!localDateString || typeof localDateString !== "string") return null;
|
||||||
|
|
||||||
const [year, month, day] = localDateString.trim().split("-");
|
|
||||||
|
|
||||||
|
const [year, month, day] = localDateString.trim().split("-");
|
||||||
if (!year || !month || !day) return null;
|
if (!year || !month || !day) return null;
|
||||||
|
|
||||||
|
const date = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day), 0, 0, 0));
|
||||||
const date = new Date(Number(year), Number(month) - 1, Number(day), 0, 0, 0);
|
|
||||||
|
|
||||||
return isNaN(date.getTime()) ? null : date.toISOString();
|
return isNaN(date.getTime()) ? null : date.toISOString();
|
||||||
}
|
}
|
@ -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 THRESH_HOLD = 48; // hours
|
||||||
export const DURATION_TIME = 10; // minutes
|
export const DURATION_TIME = 10; // minutes
|
||||||
export const ITEMS_PER_PAGE = 20;
|
export const ITEMS_PER_PAGE = 20;
|
||||||
@ -140,8 +145,17 @@ export const PROJECT_STATUS = [
|
|||||||
label: "Completed",
|
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 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";
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user