Refactor_Expenses #321

Merged
pramod.mahajan merged 249 commits from Refactor_Expenses into hotfix/MasterActivity 2025-08-01 13:14:59 +00:00
4 changed files with 144 additions and 65 deletions
Showing only changes of commit 23ee4a83d1 - Show all commits

View File

@ -23,10 +23,10 @@ const ExpenseList = () => {
}; };
const { data, isLoading, isError,isInitialLoading,error } = useExpenseList(10, currentPage, filter); const { data, isLoading, isError,isInitialLoading,error,isFetching } = useExpenseList(10, currentPage, filter);
if (isInitialLoading) return <ExpenseTableSkeleton/>; if (isInitialLoading ) return <ExpenseTableSkeleton/>;
if (isError) return <div>{error}</div>; if (isError) return <div>{error}</div>;
const items = data.data ?? []; const items = data?.data ?? [];
const totalPages = data?.totalPages ?? 1; const totalPages = data?.totalPages ?? 1;
const hasMore = currentPage < totalPages; const hasMore = currentPage < totalPages;
@ -128,7 +128,7 @@ const ExpenseList = () => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{isLoading && ( {/* {isLoading && (
<tr> <tr>
<td colSpan={7} className="text-center py-3"> <td colSpan={7} className="text-center py-3">
Loading... Loading...
@ -142,7 +142,7 @@ const ExpenseList = () => {
No expenses found. No expenses found.
</td> </td>
</tr> </tr>
)} )} */}
{!isInitialLoading && {!isInitialLoading &&
items.map((expense) => ( items.map((expense) => (

View File

@ -39,10 +39,12 @@ export const ExpenseSchema = (expenseTypes) => {
contentType: z.string().refine((val) => ALLOWED_TYPES.includes(val), { contentType: z.string().refine((val) => ALLOWED_TYPES.includes(val), {
message: "Only PDF, PNG, JPG, or JPEG files are allowed", message: "Only PDF, PNG, JPG, or JPEG files are allowed",
}), }),
documentId:z.string().optional(),
fileSize: z.number().max(MAX_FILE_SIZE, { fileSize: z.number().max(MAX_FILE_SIZE, {
message: "File size must be less than or equal to 5MB", message: "File size must be less than or equal to 5MB",
}), }),
description: z.string().optional(), description: z.string().optional(),
isActive:z.boolean().default(true)
}) })
) )
.nonempty({ message: "At least one file attachment is required" }), .nonempty({ message: "At least one file attachment is required" }),

View File

@ -19,7 +19,7 @@ import Avatar from "../common/Avatar";
import { import {
useCreateExpnse, useCreateExpnse,
useExpense, useExpense,
useUpdateExepse, useUpdateExpense,
} from "../../hooks/useExpense"; } from "../../hooks/useExpense";
import ExpenseSkeleton from "./ExpenseSkeleton"; import ExpenseSkeleton from "./ExpenseSkeleton";
@ -49,7 +49,6 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
defaultValues: defaultExpense, defaultValues: defaultExpense,
}); });
const selectedproject = watch("projectId"); const selectedproject = watch("projectId");
const selectedProject = useSelector( const selectedProject = useSelector(
(store) => store.localVariables.projectId (store) => store.localVariables.projectId
@ -72,7 +71,6 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
error: EmpError, error: EmpError,
} = useEmployeesByProject(selectedproject); } = useEmployeesByProject(selectedproject);
const files = watch("billAttachments"); const files = watch("billAttachments");
const onFileChange = async (e) => { const onFileChange = async (e) => {
const newFiles = Array.from(e.target.files); const newFiles = Array.from(e.target.files);
@ -89,6 +87,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
contentType: file.type, contentType: file.type,
fileSize: file.size, fileSize: file.size,
description: "", description: "",
isActive:true
}; };
}) })
); );
@ -114,12 +113,23 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const reader = new FileReader(); const reader = new FileReader();
reader.readAsDataURL(file); reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result.split(",")[1]); // base64 only, no prefix reader.onload = () => resolve(reader.result.split(",")[1]);
reader.onerror = (error) => reject(error); reader.onerror = (error) => reject(error);
}); });
const removeFile = (index) => { const removeFile = (index) => {
if (expenseToEdit) {
const newFiles = files.map((file, i) => {
if (file.documentId !== index) return file;
return {
...file,
isActive: false,
};
});
setValue("billAttachments", newFiles, { shouldValidate: true });
} else {
const newFiles = files.filter((_, i) => i !== index); const newFiles = files.filter((_, i) => i !== index);
setValue("billAttachments", newFiles, { shouldValidate: true }); setValue("billAttachments", newFiles, { shouldValidate: true });
}
}; };
useEffect(() => { useEffect(() => {
@ -141,15 +151,17 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
fileName: doc.fileName, fileName: doc.fileName,
base64Data: null, base64Data: null,
contentType: doc.contentType, contentType: doc.contentType,
documentId: doc.documentId,
fileSize: 0, fileSize: 0,
description: "", description: "",
preSignedUrl: doc.preSignedUrl, preSignedUrl: doc.preSignedUrl,
isActive: doc.isActive || true,
})) }))
: [], : [],
}); });
} }
}, [data, reset, employees]); }, [data, reset, employees]);
const { mutate: UpdateExpense, isPending } = useUpdateExepse(() => const { mutate: ExpenseUpdate, isPending } = useUpdateExpense(() =>
handleClose() handleClose()
); );
const { mutate: CreateExpense, isPending: createPending } = useCreateExpnse( const { mutate: CreateExpense, isPending: createPending } = useCreateExpnse(
@ -160,7 +172,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
const onSubmit = (payload) => { const onSubmit = (payload) => {
if (expenseToEdit) { if (expenseToEdit) {
const editPayload = { ...payload, id: data.id }; const editPayload = { ...payload, id: data.id };
UpdateExpense({ id: data.id, payload: editPayload }); ExpenseUpdate({ id: data.id, payload: editPayload });
} else { } else {
CreateExpense(payload); CreateExpense(payload);
} }
@ -176,12 +188,20 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
reset(); reset();
closeModal(); closeModal();
}; };
if(EmpLoading || StatusLoadding || projectLoading || ExpenseLoading || isLoading) return <ExpenseSkeleton/> if (
EmpLoading ||
StatusLoadding ||
projectLoading ||
ExpenseLoading ||
isLoading
)
return <ExpenseSkeleton />;
return ( return (
<div className="container p-3"> <div className="container p-3">
<h5 className="m-0">{expenseToEdit ? "Update Expense ": "Create New Expense"}</h5> <h5 className="m-0">
{expenseToEdit ? "Update Expense " : "Create New Expense"}
</h5>
<form id="expenseForm" onSubmit={handleSubmit(onSubmit)}> <form id="expenseForm" onSubmit={handleSubmit(onSubmit)}>
<div className="row my-2"> <div className="row my-2">
<div className="col-md-6"> <div className="col-md-6">
@ -458,10 +478,16 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
{errors.billAttachments.message} {errors.billAttachments.message}
</small> </small>
)} )}
{files.length > 0 && ( {files.length > 0 && (
<div className="d-block"> <div className="d-block">
{files.map((file, idx) => ( {files
.filter((file) => {
if (expenseToEdit) {
return file.isActive;
}
return true;
})
.map((file, idx) => (
<a <a
key={idx} key={idx}
className="d-flex justify-content-between text-start p-1" className="d-flex justify-content-between text-start p-1"
@ -481,7 +507,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
className="bx bx-trash bx-sm cursor-pointer text-danger" className="bx bx-trash bx-sm cursor-pointer text-danger"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
removeFile(idx); removeFile(expenseToEdit ? file.documentId : idx);
}} }}
></i> ></i>
</a> </a>
@ -492,9 +518,12 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
{Array.isArray(errors.billAttachments) && {Array.isArray(errors.billAttachments) &&
errors.billAttachments.map((fileError, index) => ( errors.billAttachments.map((fileError, index) => (
<div key={index} className="danger-text small mt-1"> <div key={index} className="danger-text small mt-1">
{fileError?.fileSize?.message || {
(fileError?.fileSize?.message ||
fileError?.contentType?.message || fileError?.contentType?.message ||
fileError?.base64Data?.message} fileError?.base64Data?.message,
fileError?.documentId.message)
}
</div> </div>
))} ))}
</div> </div>
@ -511,6 +540,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
</button> </button>
<button <button
type="reset" type="reset"
disabled={isPending || createPending}
onClick={handleClose} onClick={handleClose}
className="btn btn-secondary btn-sm mt-3" className="btn btn-secondary btn-sm mt-3"
> >

View File

@ -16,18 +16,16 @@ export const useExpenseList = (pageSize, pageNumber, filter) => {
}; };
export const useExpense = (ExpenseId) => { export const useExpense = (ExpenseId) => {
console.log("ExpenseId:", ExpenseId, "Enabled:", ExpenseId !== undefined && ExpenseId !== null);
return useQuery({ return useQuery({
queryKey: ["Expense", ExpenseId], queryKey: ["Expense", ExpenseId],
queryFn: async () => await ExpenseRepository.GetExpenseDetails(ExpenseId).then( queryFn: async () =>
await ExpenseRepository.GetExpenseDetails(ExpenseId).then(
(res) => res.data (res) => res.data
), ),
enabled: !!ExpenseId, enabled: !!ExpenseId,
}); });
}; };
// ---------------------------Mutation--------------------------------------------- // ---------------------------Mutation---------------------------------------------
export const useCreateExpnse = (onSuccessCallBack) => { export const useCreateExpnse = (onSuccessCallBack) => {
@ -37,8 +35,8 @@ export const useCreateExpnse = (onSuccessCallBack) => {
await ExpenseRepository.CreateExpense(payload); await ExpenseRepository.CreateExpense(payload);
}, },
onSuccess: (_, variables) => { onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ["Expenses"] });
showToast("Expense Created Successfully", "success"); showToast("Expense Created Successfully", "success");
queryClient.invalidateQueries({ queryKey: ["expenses"] });
if (onSuccessCallBack) onSuccessCallBack(); if (onSuccessCallBack) onSuccessCallBack();
}, },
onError: (error) => { onError: (error) => {
@ -50,18 +48,67 @@ export const useCreateExpnse = (onSuccessCallBack) => {
}); });
}; };
export const useUpdateExepse =()=>{ export const useUpdateExpense = (onSuccessCallBack) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({
return useMutation({ mutationFn: async ({ id, payload }) => {
mutationFn:async ({id,payload})=>{ const response = await ExpenseRepository.UpdateExpense(id, payload);
const response = await ExpenseRepository.UpdateExpense(id,payload) return response.data;
}, },
onSuccess:(updatedExpense,variables)=>{ onSuccess: (updatedExpense, variables) => {
// updation list and details // queryClient.setQueriesData(
} // {queryKey:['expenses'],exact:true},
}) // (oldData) => {
} // if (!oldData || !oldData.data) return oldData;
// const updatedList = oldData.data.map((expense) => {
// if (expense.id !== variables.id) return expense;
// return {
// ...expense,
// project:
// expense.project.id !== updatedExpense.project.id
// ? updatedExpense.project
// : expense.project,
// expensesType:
// expense.expensesType.id !== updatedExpense.expensesType.id
// ? updatedExpense.expensesType
// : expense.expensesType,
// paymentMode:
// expense.paymentMode.id !== updatedExpense.paymentMode.id
// ? updatedExpense.paymentMode
// : expense.paymentMode,
// paidBy:
// expense.paidBy.id !== updatedExpense.paidBy.id
// ? updatedExpense.paidBy
// : expense.paidBy,
// createdBy:
// expense.createdBy.id !== updatedExpense.createdBy.id
// ? updatedExpense.createdBy
// : expense.createdBy,
// createdAt: updatedExpense.createdAt,
// status: updatedExpense.status,
// nextStatus: updatedExpense.nextStatus,
// preApproved: updatedExpense.preApproved,
// transactionDate: updatedExpense.transactionDate,
// amount: updatedExpense.amount,
// };
// });
// return {
// ...oldData,
// data: updatedList,
// };
// }
// );
queryClient.removeQueries({queryKey:['Expense', variables.id]});
queryClient.invalidateQueries({queryKey:['Expenses']})
showToast('Expense updated Successfully', 'success');
if (onSuccessCallBack) onSuccessCallBack();
},
});
};
export const useActionOnExpense = (onSuccessCallBack) => { export const useActionOnExpense = (onSuccessCallBack) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();