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 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,13 @@ 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}
/>
</div> </div>
</div> </div>
@ -330,7 +336,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 +419,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 +431,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 +456,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 +475,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 +561,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 +580,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

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

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

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