changed date utils and added search employee inside manage expense

This commit is contained in:
pramod.mahajan 2025-10-04 19:50:26 +05:30
parent 6928bbd309
commit 7872e21477
6 changed files with 147 additions and 121 deletions

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,13 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
</select>
{errors.paidById && (
<small className="danger-text">{errors.paidById.message}</small>
)}
)} */}
<EmployeeSearchInput
control={control}
name="paidById"
projectId={null}
/>
</div>
</div>
@ -330,7 +336,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 +431,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 +456,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 +475,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 +580,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

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

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

@ -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();
}