Merge pull request 'HotChanges_04_10_25' (#450) from HotChanges_04_10_25 into main

Reviewed-on: #450
Merged
This commit is contained in:
pramod.mahajan 2025-10-05 04:28:55 +00:00
commit 8fd13247c7
16 changed files with 224 additions and 180 deletions

View File

@ -23,7 +23,7 @@ import Label from "../common/Label";
const ManageContact = ({ contactId, closeModal }) => {
// fetch master data
const { buckets, loading: bucketsLoaging } = useBuckets();
const { projects, loading: projectLoading } = useProjects();
const { data:projects, loading: projectLoading } = useProjects();
const { contactCategory, loading: contactCategoryLoading } =
useContactCategory();
const { organizationList } = useOrganization();

View File

@ -28,6 +28,7 @@ import moment from "moment";
import DatePicker from "../common/DatePicker";
import ErrorPage from "../../pages/ErrorPage";
import Label from "../common/Label";
import EmployeeSearchInput from "../common/EmployeeSearchInput";
const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
const {
@ -142,8 +143,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
};
useEffect(() => {
if (expenseToEdit && data ) {
if (expenseToEdit && data) {
reset({
projectId: data.project.id || "",
expensesTypeId: data.expensesType.id || "",
@ -156,7 +156,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
supplerName: data.supplerName || "",
amount: data.amount || "",
noOfPersons: data.noOfPersons || "",
gstNumber:data.gstNumber || "",
gstNumber: data.gstNumber || "",
billAttachments: data.documents
? data.documents.map((doc) => ({
fileName: doc.fileName,
@ -183,8 +183,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
const onSubmit = (fromdata) => {
let payload = {
...fromdata,
transactionDate: localToUtc(fromdata.transactionDate)
transactionDate: localToUtc(fromdata.transactionDate),
};
if (expenseToEdit) {
const editPayload = { ...payload, id: data.id };
@ -206,7 +205,6 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
if (StatusLoadding || projectLoading || ExpenseLoading || isLoading)
return <ExpenseSkeleton />;
return (
<div className="container p-3">
<h5 className="m-0">
@ -215,7 +213,9 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
<form id="expenseForm" onSubmit={handleSubmit(onSubmit)}>
<div className="row my-2 text-start">
<div className="col-md-6">
<Label className="form-label" required>Select Project</Label>
<Label className="form-label" required>
Select Project
</Label>
<select
className="form-select form-select-sm"
{...register("projectId")}
@ -296,11 +296,11 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
)}
</div>
<div className="col-md-6">
<div className="col-12 col-md-6 text-start">
<Label htmlFor="paidById" className="form-label" required>
Paid By
</Label>
<select
{/* <select
className="form-select form-select-sm"
id="paymentModeId"
{...register("paidById")}
@ -321,7 +321,14 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
</select>
{errors.paidById && (
<small className="danger-text">{errors.paidById.message}</small>
)}
)} */}
<EmployeeSearchInput
control={control}
name="paidById"
projectId={null}
forAll={expenseToEdit ? true : false}
/>
</div>
</div>
@ -330,7 +337,11 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
<Label htmlFor="transactionDate" className="form-label" required>
Transaction Date
</Label>
<DatePicker name="transactionDate" control={control} maxDate={new Date()}/>
<DatePicker
name="transactionDate"
control={control}
maxDate={new Date()}
/>
{errors.transactionDate && (
<small className="danger-text">
@ -421,9 +432,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
{...register("gstNumber")}
/>
{errors.gstNumber && (
<small className="danger-text">
{errors.gstNumber.message}
</small>
<small className="danger-text">{errors.gstNumber.message}</small>
)}
</div>
@ -448,7 +457,9 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
<div className="row my-2 text-start">
<div className="col-md-12">
<Label htmlFor="description" className="form-label" required>Description</Label>
<Label htmlFor="description" className="form-label" required>
Description
</Label>
<textarea
id="description"
className="form-control form-control-sm"
@ -465,7 +476,9 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
<div className="row my-2 text-start">
<div className="col-md-12">
<Label className="form-label" required>Upload Bill </Label>
<Label className="form-label" required>
Upload Bill{" "}
</Label>
<div
className="border border-secondary border-dashed rounded p-4 text-center bg-textMuted position-relative"
@ -568,7 +581,6 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
? "Update"
: "Submit"}
</button>
</div>
</form>
</div>

View File

@ -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 {
@ -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 }) => {
<DatePicker
name="reimburseDate"
control={control}
minDate={data?.transactionDate}
minDate={data?.createdAt}
maxDate={new Date()}
/>
{errors.reimburseDate && (
<small className="danger-text">

View File

@ -23,7 +23,7 @@ const OrganizationsList = ({searchText}) => {
label: "Organization Name",
getValue: (org) => (
<div className="d-flex gap-2 py-1 ">
<i class="bx bx-buildings"></i>
<i className="bx bx-buildings"></i>
<span
className="text-truncate d-inline-block "
style={{ maxWidth: "150px" }}

View File

@ -4,10 +4,13 @@ import { useDebounce } from "../../utils/appUtils";
import { useController } from "react-hook-form";
import Avatar from "./Avatar";
const EmployeeSearchInput = ({ control, name, projectId,placeholder }) => {
const EmployeeSearchInput = ({
control,
name,
projectId,
placeholder,
forAll,
}) => {
const {
field: { onChange, value, ref },
fieldState: { error },
@ -17,17 +20,20 @@ const EmployeeSearchInput = ({ control, name, projectId,placeholder }) => {
const [showDropdown, setShowDropdown] = useState(false);
const debouncedSearch = useDebounce(search, 500);
const {
data: employees,
isLoading,
} = useEmployeesName(projectId, debouncedSearch);
const { data: employees, isLoading } = useEmployeesName(
projectId,
debouncedSearch,
forAll
);
useEffect(() => {
if (value && !search) {
const found = employees?.data?.find((emp) => emp.id === value);
if (found) setSearch(found.firstName + " " + found.lastName);
if (value && employees?.data) {
const found = employees.data.find((emp) => emp.id === value);
if (found && forAll) {
setSearch(found.firstName + " " + found.lastName);
}
}, [value, employees]);
}
}, [value, employees?.data]);
const handleSelect = (employee) => {
onChange(employee.id);
@ -61,7 +67,6 @@ const EmployeeSearchInput = ({ control, name, projectId,placeholder }) => {
{isLoading ? (
<li className="list-group-item">
<a>Searching...</a>
</li>
) : (
employees?.data?.map((emp) => (

View File

@ -548,6 +548,7 @@ export const useUpdateNote = (onSuccessCallBack) => {
mutationFn: async ({ noteId, notePayload }) =>
await DirectoryRepository.UpdateNote(noteId, notePayload),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ["Contact"] });
queryClient.invalidateQueries({ queryKey: ["Notes"] });
queryClient.invalidateQueries({ queryKey: ["ContactNotes"] });
showToast("Note updated Successfully", "success");

View File

@ -184,11 +184,11 @@ export const useEmployeeProfile = (employeeId) => {
};
};
export const useEmployeesName = (projectId, search) => {
export const useEmployeesName = (projectId, search,allEmployee) => {
return useQuery({
queryKey: ["employees", projectId, search],
queryKey: ["employees", projectId, search,allEmployee],
queryFn: async () =>
await EmployeeRepository.getEmployeeName(projectId, search),
await EmployeeRepository.getEmployeeName(projectId, search,allEmployee),
staleTime: 5 * 60 * 1000, // Optional: cache for 5 minutes
});

View File

@ -14,7 +14,6 @@ export const useProjectAccess = (projectId) => {
const canView = useHasUserPermission(VIEW_PROJECTS);
const loading = isLoading || !isFetched;
debugger
useEffect(() => {
if (projectId && !loading && !canView) {
showToast("You don't have permission to view project details", "warning");

View File

@ -6,6 +6,7 @@ import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { AuthWrapper } from "./AuthWrapper";
import { removeSession } from "../../utils/authUtils";
const LoginPage = () => {
const navigate = useNavigate();
@ -44,9 +45,11 @@ const LoginPage = () => {
if (data.rememberMe) {
localStorage.setItem("jwtToken", response.data.token);
localStorage.setItem("refreshToken", response.data.refreshToken);
removeSession("session")
} else {
sessionStorage.setItem("jwtToken", response.data.token);
sessionStorage.setItem("refreshToken", response.data.refreshToken);
removeSession("local")
}
setLoading(false);
navigate("/auth/switch/org");

View File

@ -86,7 +86,7 @@ const SwitchTenant = () => {
<button
className={` ${currentTenant === tenant.id ? "badge bg-label-primary w-50" :"btn btn-primary btn-sm mt-2 align-self-start" }`}
onClick={() => handleTenantselection(tenant?.id)}
disabled={isPending && pendingTenant === tenant.id || currentTenant === tenant.id }
disabled={isPending && pendingTenant === tenant.id || currentTenant === tenant.id || isPending}
>
{currentTenant === tenant.id ? "Active Tenant" :isPending && pendingTenant === tenant.id
? "Please Wait.."

View File

@ -11,11 +11,12 @@ const EmployeeRepository = {
// deleteEmployee: ( id ) => api.delete( `/users/${ id }` ),
getEmployeeProfile: (id) => api.get(`/api/employee/profile/get/${id}`),
deleteEmployee: (id,active) => api.delete(`/api/employee/${id}?active=${active}`),
getEmployeeName: (projectId, search) => {
getEmployeeName: (projectId, search,allEmployee) => {
const params = new URLSearchParams();
if (projectId) params.append("projectId", projectId);
if (search) params.append("searchString", search);
if(allEmployee) params.append("allEmployee",allEmployee)
const query = params.toString();
return api.get(`/api/Employee/basic${query ? `?${query}` : ""}`);

View File

@ -18,7 +18,7 @@ const ProjectRepository = {
}
return api.get(url);
},
},
getEmployeesByProject: (projectId) =>
@ -43,12 +43,13 @@ const ProjectRepository = {
projectNameList: () => api.get("/api/project/list/basic"),
getProjectDetails: (id) => api.get(`/api/project/details/${id}`),
getProjectInfraByproject: (projectId, serviceId) => {
let url = `/api/project/infra-details/${projectId}`;
if (serviceId) {
url + `?serviceId=${serviceId}`;
url = `${url}?serviceId=${serviceId}`;
}
return api.get(url);
},
getProjectTasksByWorkArea: (workAreaId, serviceId) => {

View File

@ -59,7 +59,7 @@ const attemptTokenRefresh = async (storedRefreshToken) => {
return true;
} catch (error) {
console.error("Token refresh failed:", error);
removeSession()
return false;
}
};

View File

@ -8,7 +8,7 @@ import showToast from "./toastService";
import eventBus from "./eventBus";
import { useSelector } from "react-redux";
import { clearApiCacheKey } from "../slices/apiCacheSlice";
import {BASE_URL} from "../utils/constants";
import { BASE_URL } from "../utils/constants";
import { queryClient } from "../layouts/AuthLayout";
const base_Url = BASE_URL;
let connection = null;
@ -32,103 +32,108 @@ export function startSignalR(loggedUser) {
.toISOString()
.split("T")[0];
connection.on("NotificationEventHandler", (data) => {
if (data.loggedInUserId != loggedUser?.employeeInfo.id) {
// console.log("Notification received:", data);
// if action taken on attendance module
if (data.keyword == "Attendance") {
const checkIn = data.response.checkInTime.substring(0, 10);
if (today === checkIn) {
eventBus.emit("attendance", data);
}
var onlyDate = Number(checkIn.substring(8, 10));
const { loggedInUserId, keyword, response, employeeList, numberOfImages } =
data;
const loggedInId = loggedUser?.employeeInfo?.id;
var afterTwoDay =
checkIn.substring(0, 8) + (onlyDate + 2).toString().padStart(2, "0");
if (loggedInUserId === loggedInId) return;
// ---- Handlers for invalidate or remove ----
const queryInvalidators = {
Expanse: () => {
queryClient.invalidateQueries({ queryKey: ["Expenses"] }),
queryClient.invalidateQueries({ queryKey: ["Expense"] });
},
Create_Project: () => queryClient.invalidateQueries(["projectslist"]),
Update_Project: () => queryClient.invalidateQueries(["projectslist"]),
Infra: () => queryClient.removeQueries({ queryKey: ["ProjectInfra"] }),
Task_Report: () => queryClient.invalidateQueries({ queryKey: ["Infra"] }),
WorkItem: () =>
queryClient.invalidateQueries({ queryKey: ["WorkItems"] }),
Directory_Notes: () => {
queryClient.invalidateQueries({ queryKey: ["directoryNotes"] });
queryClient.invalidateQueries({ queryKey: ["Notes"] });
},
Directory_Buckets: () => {
queryClient.invalidateQueries({ queryKey: ["bucketList"] });
},
Directory: () => {
queryClient.invalidateQueries({ queryKey: ["contacts"] });
queryClient.invalidateQueries({ queryKey: ["Contact"] });
queryClient.invalidateQueries({ queryKey: ["ContactProfile"] });
},
};
// ---- Keyword based event emitters ----
const emitters = {
employee: () => eventBus.emit("employee", data),
project: () => eventBus.emit("project", data),
infra: () => eventBus.emit("infra", data),
assign_project_all: () => eventBus.emit("assign_project_all", data),
image_gallery: () => eventBus.emit("image_gallery", data),
};
// ---- Handle Attendance ----
if (keyword === "Attendance") {
const checkIn = response.checkInTime.substring(0, 10);
if (today === checkIn) eventBus.emit("attendance", data);
const onlyDate = Number(checkIn.substring(8, 10));
const afterTwoDay = checkIn.substring(0,8) + (onlyDate + 2).toString().padStart(2, "0");
if (
afterTwoDay <= today &&
(data.response.activity == 4 || data.response.activity == 5)
(response.activity === 4 || response.activity === 5)
) {
eventBus.emit("regularization", data);
}
eventBus.emit("attendance_log", data);
}
if(data.keyword == "Expanse"){
queryClient.invalidateQueries({queryKey:["Expenses"]})
}
// if create or update project
if (
data.keyword == "Create_Project" ||
data.keyword == "Update_Project"
) {
// clearCacheKey("projectslist");
queryClient.invalidateQueries(['projectslist']);
eventBus.emit("project", data);
// ---- Invalidate/Remove cache by keywords ----
if (queryInvalidators[keyword]) {
queryInvalidators[keyword]();
}
// if assign or deassign employee to any project
if (data.keyword == "Assign_Project") {
if (
data.employeeList.some((item) => item === loggedUser?.employeeInfo.id)
) {
// ---- Project creation/update ----
if (keyword === "Create_Project" || keyword === "Update_Project") {
emitters.project();
}
// ---- Assign/deassign project ----
if (keyword === "Assign_Project") {
if (employeeList?.includes(loggedInId)) {
try {
cacheData("hasReceived", false);
eventBus.emit("assign_project_one", data);
} catch (e) {
// console.error("Error in cacheData:", e);
} catch {}
}
}
eventBus.emit("assign_project_all", data);
}
// if created or updated infra
if (data.keyword == "Infra") {
queryClient.removeQueries({queryKey:["ProjectInfra"]})
// eventBus.emit("infra", data);
}
if (data.keyword == "Task_Report") {
queryClient.removeQueries({queryKey:["Infra"]})
// eventBus.emit("infra", data);
emitters.assign_project_all();
}
if ( data.keyword == "WorkItem" )
{
queryClient.removeQueries({queryKey:["WorkItems"]})
}
// if created or updated Employee
if (data.keyword == "Employee") {
// clearCacheKey("employeeListByProject");
// clearCacheKey("allEmployeeList");
// clearCacheKey("allInactiveEmployeeList");
// clearCacheKey("employeeProfile");
// ---- Employee update ----
if (keyword === "Employee") {
clearCacheKey("Attendance");
clearCacheKey("regularizedList")
clearCacheKey("AttendanceLogs")
clearCacheKey("regularizedList");
clearCacheKey("AttendanceLogs");
// ---we can do also----
// queryClient.removeQueries(['allEmployee', true]);
// but best practies is refetch
queryClient.invalidateQueries(['allEmployee', true]);
queryClient.invalidateQueries(['allEmployee', false]);
queryClient.invalidateQueries(['employeeProfile', data.response?.employeeId]);
queryClient.invalidateQueries(['employeeListByProject']); // optional if scope
eventBus.emit("employee", data);
queryClient.invalidateQueries(["allEmployee", true]);
queryClient.invalidateQueries(["allEmployee", false]);
queryClient.invalidateQueries(["employeeProfile", response?.employeeId]);
queryClient.invalidateQueries(["employeeListByProject"]);
emitters.employee();
}
if (data.keyword == "Task_Report") {
if(data.numberOfImages > 0){
eventBus.emit("image_gallery", data);
}
}
if (data.keyword == "Task_Comment") {
if(data.numberOfImages > 0){
eventBus.emit("image_gallery", data);
}
}
// ---- Image related events ----
if (
["Task_Report", "Task_Comment"].includes(keyword) &&
numberOfImages > 0
) {
emitters.image_gallery();
}
});
connection
.start();
connection.start();
}
export function stopSignalR() {

View File

@ -69,15 +69,26 @@ export const normalizeAllowedContentTypes = (allowedContentType) => {
return allowedContentType.split(",");
return [];
};
export function localToUtc(localDateString) {
if (!localDateString || typeof localDateString !== "string") return null;
export function localToUtc(dateString) {
if (!dateString || typeof dateString !== "string") return null;
const [year, month, day] = localDateString.trim().split("-");
const parts = dateString.trim().split("-");
if (parts.length !== 3) return null;
if (!year || !month || !day) return null;
let day, month, year;
const date = new Date(Number(year), Number(month) - 1, Number(day), 0, 0, 0);
return isNaN(date.getTime()) ? null : date.toISOString();
if (parts[0].length === 4) {
// Format: yyyy-mm-dd
[year, month, day] = parts;
} else {
// Format: dd-mm-yyyy
[day, month, year] = parts;
}
if (!day || !month || !year) return null;
const date = new Date(
Date.UTC(Number(year), Number(month) - 1, Number(day), 0, 0, 0)
);
return isNaN(date.getTime()) ? null : date.toISOString();
}

View File

@ -1,7 +1,14 @@
export const removeSession = () => {
localStorage.removeItem("jwtToken");
localStorage.removeItem("refreshToken");
sessionStorage.removeItem("jwtToken");
sessionStorage.removeItem("refreshToken");
localStorage.removeItem("ctnt");
export const removeSession = (target = "all") => {
const keys = ["jwtToken", "refreshToken", "ctnt"];
if (target === "local") {
keys.forEach((key) => localStorage.removeItem(key));
} else if (target === "session") {
keys.forEach((key) => sessionStorage.removeItem(key));
} else if (target === "all") {
keys.forEach((key) => {
localStorage.removeItem(key);
sessionStorage.removeItem(key);
});
}
};