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 }) => { const ManageContact = ({ contactId, closeModal }) => {
// fetch master data // fetch master data
const { buckets, loading: bucketsLoaging } = useBuckets(); const { buckets, loading: bucketsLoaging } = useBuckets();
const { projects, loading: projectLoading } = useProjects(); const { data:projects, loading: projectLoading } = useProjects();
const { contactCategory, loading: contactCategoryLoading } = const { contactCategory, loading: contactCategoryLoading } =
useContactCategory(); useContactCategory();
const { organizationList } = useOrganization(); const { organizationList } = useOrganization();

View File

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

View File

@ -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 {
@ -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">

View File

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

View File

@ -4,10 +4,13 @@ import { useDebounce } from "../../utils/appUtils";
import { useController } from "react-hook-form"; import { useController } from "react-hook-form";
import Avatar from "./Avatar"; import Avatar from "./Avatar";
const EmployeeSearchInput = ({
control,
name,
const EmployeeSearchInput = ({ control, name, projectId,placeholder }) => { projectId,
placeholder,
forAll,
}) => {
const { const {
field: { onChange, value, ref }, field: { onChange, value, ref },
fieldState: { error }, fieldState: { error },
@ -17,17 +20,20 @@ const EmployeeSearchInput = ({ control, name, projectId,placeholder }) => {
const [showDropdown, setShowDropdown] = useState(false); const [showDropdown, setShowDropdown] = useState(false);
const debouncedSearch = useDebounce(search, 500); const debouncedSearch = useDebounce(search, 500);
const { const { data: employees, isLoading } = useEmployeesName(
data: employees, projectId,
isLoading, debouncedSearch,
} = useEmployeesName(projectId, debouncedSearch); forAll
);
useEffect(() => { useEffect(() => {
if (value && !search) { if (value && employees?.data) {
const found = employees?.data?.find((emp) => emp.id === value); const found = employees.data.find((emp) => emp.id === value);
if (found) setSearch(found.firstName + " " + found.lastName); if (found && forAll) {
setSearch(found.firstName + " " + found.lastName);
}
} }
}, [value, employees]); }, [value, employees?.data]);
const handleSelect = (employee) => { const handleSelect = (employee) => {
onChange(employee.id); onChange(employee.id);
@ -46,7 +52,7 @@ const EmployeeSearchInput = ({ control, name, projectId,placeholder }) => {
onChange={(e) => { onChange={(e) => {
setSearch(e.target.value); setSearch(e.target.value);
setShowDropdown(true); setShowDropdown(true);
onChange(""); onChange("");
}} }}
onFocus={() => { onFocus={() => {
if (search) setShowDropdown(true); if (search) setShowDropdown(true);
@ -61,28 +67,27 @@ const EmployeeSearchInput = ({ control, name, projectId,placeholder }) => {
{isLoading ? ( {isLoading ? (
<li className="list-group-item"> <li className="list-group-item">
<a>Searching...</a> <a>Searching...</a>
</li> </li>
) : ( ) : (
employees?.data?.map((emp) => ( employees?.data?.map((emp) => (
<li <li
key={emp.id} key={emp.id}
className="list-group-item list-group-item-action py-1 px-1" className="list-group-item list-group-item-action py-1 px-1"
style={{ cursor: "pointer" }} style={{ cursor: "pointer" }}
onClick={() => handleSelect(emp)} onClick={() => handleSelect(emp)}
> >
<div className="d-flex align-items-center px-0"> <div className="d-flex align-items-center px-0">
<Avatar <Avatar
size="xs" size="xs"
classAvatar="m-0 me-2" classAvatar="m-0 me-2"
firstName={emp.firstName} firstName={emp.firstName}
lastName={emp.lastName} lastName={emp.lastName}
/> />
<span className="text-muted"> <span className="text-muted">
{`${emp?.firstName} ${emp?.lastName}`.trim()} {`${emp?.firstName} ${emp?.lastName}`.trim()}
</span> </span>
</div> </div>
</li> </li>
)) ))
)} )}
</ul> </ul>

View File

@ -548,6 +548,7 @@ export const useUpdateNote = (onSuccessCallBack) => {
mutationFn: async ({ noteId, notePayload }) => mutationFn: async ({ noteId, notePayload }) =>
await DirectoryRepository.UpdateNote(noteId, notePayload), await DirectoryRepository.UpdateNote(noteId, notePayload),
onSuccess: (_, variables) => { onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ["Contact"] });
queryClient.invalidateQueries({ queryKey: ["Notes"] }); queryClient.invalidateQueries({ queryKey: ["Notes"] });
queryClient.invalidateQueries({ queryKey: ["ContactNotes"] }); queryClient.invalidateQueries({ queryKey: ["ContactNotes"] });
showToast("Note updated Successfully", "success"); 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({ return useQuery({
queryKey: ["employees", projectId, search], queryKey: ["employees", projectId, search,allEmployee],
queryFn: async () => queryFn: async () =>
await EmployeeRepository.getEmployeeName(projectId, search), await EmployeeRepository.getEmployeeName(projectId, search,allEmployee),
staleTime: 5 * 60 * 1000, // Optional: cache for 5 minutes 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 canView = useHasUserPermission(VIEW_PROJECTS);
const loading = isLoading || !isFetched; const loading = isLoading || !isFetched;
debugger
useEffect(() => { useEffect(() => {
if (projectId && !loading && !canView) { if (projectId && !loading && !canView) {
showToast("You don't have permission to view project details", "warning"); 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 { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { AuthWrapper } from "./AuthWrapper"; import { AuthWrapper } from "./AuthWrapper";
import { removeSession } from "../../utils/authUtils";
const LoginPage = () => { const LoginPage = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@ -44,9 +45,11 @@ const LoginPage = () => {
if (data.rememberMe) { if (data.rememberMe) {
localStorage.setItem("jwtToken", response.data.token); localStorage.setItem("jwtToken", response.data.token);
localStorage.setItem("refreshToken", response.data.refreshToken); localStorage.setItem("refreshToken", response.data.refreshToken);
removeSession("session")
} else { } else {
sessionStorage.setItem("jwtToken", response.data.token); sessionStorage.setItem("jwtToken", response.data.token);
sessionStorage.setItem("refreshToken", response.data.refreshToken); sessionStorage.setItem("refreshToken", response.data.refreshToken);
removeSession("local")
} }
setLoading(false); setLoading(false);
navigate("/auth/switch/org"); navigate("/auth/switch/org");

View File

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

View File

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

View File

@ -6,19 +6,19 @@ const ProjectRepository = {
api.get(`/api/project/details/${projetid}`), api.get(`/api/project/details/${projetid}`),
getProjectAllocation: (projectId, serviceId, organizationId, employeeStatus) => { getProjectAllocation: (projectId, serviceId, organizationId, employeeStatus) => {
let url = `/api/project/allocation/${projectId}`; let url = `/api/project/allocation/${projectId}`;
const params = []; const params = [];
if (organizationId) params.push(`organizationId=${organizationId}`); if (organizationId) params.push(`organizationId=${organizationId}`);
if (serviceId) params.push(`serviceId=${serviceId}`); if (serviceId) params.push(`serviceId=${serviceId}`);
if (employeeStatus !== undefined) params.push(`includeInactive=${employeeStatus}`); if (employeeStatus !== undefined) params.push(`includeInactive=${employeeStatus}`);
if (params.length > 0) { if (params.length > 0) {
url += `?${params.join("&")}`; url += `?${params.join("&")}`;
} }
return api.get(url); return api.get(url);
}, },
getEmployeesByProject: (projectId) => getEmployeesByProject: (projectId) =>
@ -43,12 +43,13 @@ const ProjectRepository = {
projectNameList: () => api.get("/api/project/list/basic"), projectNameList: () => api.get("/api/project/list/basic"),
getProjectDetails: (id) => api.get(`/api/project/details/${id}`), getProjectDetails: (id) => api.get(`/api/project/details/${id}`),
getProjectInfraByproject: (projectId, serviceId) => { getProjectInfraByproject: (projectId, serviceId) => {
let url = `/api/project/infra-details/${projectId}`; let url = `/api/project/infra-details/${projectId}`;
if (serviceId) { if (serviceId) {
url + `?serviceId=${serviceId}`; url = `${url}?serviceId=${serviceId}`;
} }
return api.get(url); return api.get(url);
}, },
getProjectTasksByWorkArea: (workAreaId, serviceId) => { getProjectTasksByWorkArea: (workAreaId, serviceId) => {

View File

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

View File

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

View File

@ -69,15 +69,26 @@ export const normalizeAllowedContentTypes = (allowedContentType) => {
return allowedContentType.split(","); return allowedContentType.split(",");
return []; return [];
}; };
export function localToUtc(localDateString) { export function localToUtc(dateString) {
if (!localDateString || typeof localDateString !== "string") return null; 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;
if (parts[0].length === 4) {
const date = new Date(Number(year), Number(month) - 1, Number(day), 0, 0, 0); // Format: yyyy-mm-dd
[year, month, day] = parts;
return isNaN(date.getTime()) ? null : date.toISOString(); } 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 = () => { export const removeSession = (target = "all") => {
localStorage.removeItem("jwtToken"); const keys = ["jwtToken", "refreshToken", "ctnt"];
localStorage.removeItem("refreshToken");
sessionStorage.removeItem("jwtToken"); if (target === "local") {
sessionStorage.removeItem("refreshToken"); keys.forEach((key) => localStorage.removeItem(key));
localStorage.removeItem("ctnt"); } else if (target === "session") {
}; keys.forEach((key) => sessionStorage.removeItem(key));
} else if (target === "all") {
keys.forEach((key) => {
localStorage.removeItem(key);
sessionStorage.removeItem(key);
});
}
};