completed full updation for expnse feature

This commit is contained in:
pramod mahajan 2025-07-24 12:35:12 +05:30
parent 289a732600
commit de689b0af8
4 changed files with 144 additions and 65 deletions

View File

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

View File

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

View File

@ -19,7 +19,7 @@ import Avatar from "../common/Avatar";
import {
useCreateExpnse,
useExpense,
useUpdateExepse,
useUpdateExpense,
} from "../../hooks/useExpense";
import ExpenseSkeleton from "./ExpenseSkeleton";
@ -49,7 +49,6 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
defaultValues: defaultExpense,
});
const selectedproject = watch("projectId");
const selectedProject = useSelector(
(store) => store.localVariables.projectId
@ -72,7 +71,6 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
error: EmpError,
} = useEmployeesByProject(selectedproject);
const files = watch("billAttachments");
const onFileChange = async (e) => {
const newFiles = Array.from(e.target.files);
@ -89,6 +87,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
contentType: file.type,
fileSize: file.size,
description: "",
isActive:true
};
})
);
@ -114,12 +113,23 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
new Promise((resolve, reject) => {
const reader = new FileReader();
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);
});
const removeFile = (index) => {
const newFiles = files.filter((_, i) => i !== index);
setValue("billAttachments", newFiles, { shouldValidate: true });
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);
setValue("billAttachments", newFiles, { shouldValidate: true });
}
};
useEffect(() => {
@ -139,17 +149,19 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
billAttachments: data.documents
? data.documents.map((doc) => ({
fileName: doc.fileName,
base64Data: null,
base64Data: null,
contentType: doc.contentType,
documentId: doc.documentId,
fileSize: 0,
description: "",
preSignedUrl: doc.preSignedUrl,
preSignedUrl: doc.preSignedUrl,
isActive: doc.isActive || true,
}))
: [],
});
}
}, [data, reset, employees]);
const { mutate: UpdateExpense, isPending } = useUpdateExepse(() =>
const { mutate: ExpenseUpdate, isPending } = useUpdateExpense(() =>
handleClose()
);
const { mutate: CreateExpense, isPending: createPending } = useCreateExpnse(
@ -160,7 +172,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
const onSubmit = (payload) => {
if (expenseToEdit) {
const editPayload = { ...payload, id: data.id };
UpdateExpense({ id: data.id, payload: editPayload });
ExpenseUpdate({ id: data.id, payload: editPayload });
} else {
CreateExpense(payload);
}
@ -176,12 +188,20 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
reset();
closeModal();
};
if(EmpLoading || StatusLoadding || projectLoading || ExpenseLoading || isLoading) return <ExpenseSkeleton/>
if (
EmpLoading ||
StatusLoadding ||
projectLoading ||
ExpenseLoading ||
isLoading
)
return <ExpenseSkeleton />;
return (
<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)}>
<div className="row my-2">
<div className="col-md-6">
@ -458,43 +478,52 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
{errors.billAttachments.message}
</small>
)}
{files.length > 0 && (
<div className="d-block">
{files.map((file, idx) => (
<a
key={idx}
className="d-flex justify-content-between text-start p-1"
href={file.preSignedUrl || "#"}
target="_blank"
rel="noopener noreferrer"
>
<div>
<span className="mb-0 text-secondary small d-block">
{file.fileName}
</span>
<span className="text-body-secondary small d-block">
{file.fileSize ? formatFileSize(file.fileSize) : ""}
</span>
</div>
<i
className="bx bx-trash bx-sm cursor-pointer text-danger"
onClick={(e) => {
e.preventDefault();
removeFile(idx);
}}
></i>
</a>
))}
{files
.filter((file) => {
if (expenseToEdit) {
return file.isActive;
}
return true;
})
.map((file, idx) => (
<a
key={idx}
className="d-flex justify-content-between text-start p-1"
href={file.preSignedUrl || "#"}
target="_blank"
rel="noopener noreferrer"
>
<div>
<span className="mb-0 text-secondary small d-block">
{file.fileName}
</span>
<span className="text-body-secondary small d-block">
{file.fileSize ? formatFileSize(file.fileSize) : ""}
</span>
</div>
<i
className="bx bx-trash bx-sm cursor-pointer text-danger"
onClick={(e) => {
e.preventDefault();
removeFile(expenseToEdit ? file.documentId : idx);
}}
></i>
</a>
))}
</div>
)}
{Array.isArray(errors.billAttachments) &&
errors.billAttachments.map((fileError, index) => (
<div key={index} className="danger-text small mt-1">
{fileError?.fileSize?.message ||
fileError?.contentType?.message ||
fileError?.base64Data?.message}
{
(fileError?.fileSize?.message ||
fileError?.contentType?.message ||
fileError?.base64Data?.message,
fileError?.documentId.message)
}
</div>
))}
</div>
@ -511,6 +540,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
</button>
<button
type="reset"
disabled={isPending || createPending}
onClick={handleClose}
className="btn btn-secondary btn-sm mt-3"
>

View File

@ -16,18 +16,16 @@ export const useExpenseList = (pageSize, pageNumber, filter) => {
};
export const useExpense = (ExpenseId) => {
console.log("ExpenseId:", ExpenseId, "Enabled:", ExpenseId !== undefined && ExpenseId !== null);
return useQuery({
queryKey: ["Expense", ExpenseId],
queryFn: async () => await ExpenseRepository.GetExpenseDetails(ExpenseId).then(
queryFn: async () =>
await ExpenseRepository.GetExpenseDetails(ExpenseId).then(
(res) => res.data
),
enabled: !!ExpenseId,
enabled: !!ExpenseId,
});
};
// ---------------------------Mutation---------------------------------------------
export const useCreateExpnse = (onSuccessCallBack) => {
@ -37,8 +35,8 @@ export const useCreateExpnse = (onSuccessCallBack) => {
await ExpenseRepository.CreateExpense(payload);
},
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ["Expenses"] });
showToast("Expense Created Successfully", "success");
queryClient.invalidateQueries({ queryKey: ["expenses"] });
if (onSuccessCallBack) onSuccessCallBack();
},
onError: (error) => {
@ -50,18 +48,67 @@ export const useCreateExpnse = (onSuccessCallBack) => {
});
};
export const useUpdateExepse =()=>{
const queryClient = useQueryClient();
export const useUpdateExpense = (onSuccessCallBack) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ id, payload }) => {
const response = await ExpenseRepository.UpdateExpense(id, payload);
return response.data;
},
onSuccess: (updatedExpense, variables) => {
// queryClient.setQueriesData(
// {queryKey:['expenses'],exact:true},
// (oldData) => {
// if (!oldData || !oldData.data) return oldData;
return useMutation({
mutationFn:async ({id,payload})=>{
const response = await ExpenseRepository.UpdateExpense(id,payload)
},
onSuccess:(updatedExpense,variables)=>{
// updation list and details
}
})
}
// 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) => {
const queryClient = useQueryClient();