Merge branch 'Feature_Expense' of https://git.marcoaiot.com/admin/marco.pms.web into Feature#796_EditExpense

This commit is contained in:
pramod mahajan 2025-07-23 18:03:52 +05:30
commit 5e93798d01
5 changed files with 202 additions and 62 deletions

View File

@ -185,7 +185,7 @@ const ExpenseList = () => {
<span
className="cursor-pointer"
onClick={() =>
setViewExpense({ expenseId: expense, view: true })
setViewExpense({ expenseId:expense.id, view: true })
}
>
<i className="bx bx-show text-primary "></i>

View File

@ -0,0 +1,28 @@
import { useState } from 'react';
const PreviewDocument = ({ imageUrl }) => {
const [loading, setLoading] = useState(true);
return (
<div className="d-flex justify-content-center align-items-center" style={{ minHeight: "50vh" }}>
{loading && (
<div className="text-secondary text-center mb-2">
Loading...
</div>
)}
<img
src={imageUrl}
alt="Full View"
className="img-fluid"
style={{
maxHeight: "100vh",
objectFit: "contain",
display: loading ? "none" : "block",
}}
onLoad={() => setLoading(false)}
/>
</div>
);
};
export default PreviewDocument;

View File

@ -1,12 +1,16 @@
import React from "react";
import { useActionOnExpense } from "../../hooks/useExpense";
import React, { useState } from "react";
import { useActionOnExpense, useExpense } from "../../hooks/useExpense";
import { formatUTCToLocalTime } from "../../utils/dateUtils";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { ActionSchema } from "./ExpenseSchema";
import { getColorNameFromHex } from "../../utils/appUtils";
import { useExpenseContext } from "../../pages/Expense/ExpensePage";
const ViewExpense = ({ ExpenseId }) => {
const { data, isLoading, isError, error } = useExpense(ExpenseId);
const [imageLoaded, setImageLoaded] = useState({});
const { setDocumentView } = useExpenseContext();
const {
register,
handleSubmit,
@ -32,6 +36,20 @@ const ViewExpense = ({ ExpenseId }) => {
MakeAction(Payload);
};
if (isLoading) {
return (
<div
className="d-flex justify-content-center align-items-center"
style={{ minHeight: "300px" }}
>
<div className="fs-5 text-muted">Loading...</div>
</div>
);
}
const handleImageLoad = (id) => {
setImageLoaded((prev) => ({ ...prev, [id]: true }));
};
return (
<form className="container px-3" onSubmit={handleSubmit(onSubmit)}>
<div className="row mb-3">
@ -43,48 +61,61 @@ const ViewExpense = ({ ExpenseId }) => {
{/* Expense Info Rows */}
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold">Transaction Date :</label>
<div className="text-muted">{formatUTCToLocalTime(ExpenseId.transactionDate)}</div>
</div>
</div>
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold">Expense Type :</label>
<div className="text-muted">{ExpenseId.expensesType.name}</div>
</div>
</div>
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold">Supplier :</label>
<div className="text-muted">{ExpenseId.supplerName}</div>
</div>
</div>
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold">Amount :</label>
<div className="text-muted"> {ExpenseId.amount}</div>
</div>
</div>
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold">Payment Mode :</label>
<div className="text-muted">{ExpenseId.paymentMode.name}</div>
</div>
</div>
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold">Paid By :</label>
<label className="form-label me-2 mb-0 fw-semibold">
Transaction Date :
</label>
<div className="text-muted">
{ExpenseId.paidBy.firstName} {ExpenseId.paidBy.lastName}
{formatUTCToLocalTime(data.transactionDate)}
</div>
</div>
</div>
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold">
Expense Type :
</label>
<div className="text-muted">{data.expensesType.name}</div>
</div>
</div>
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold">
Supplier :
</label>
<div className="text-muted">{data.supplerName}</div>
</div>
</div>
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold">Amount :</label>
<div className="text-muted"> {data.amount}</div>
</div>
</div>
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold">
Payment Mode :
</label>
<div className="text-muted">{data.paymentMode.name}</div>
</div>
</div>
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold">
Paid By :
</label>
<div className="text-muted">
{data.paidBy.firstName} {data.paidBy.lastName}
</div>
</div>
</div>
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="d-flex align-items-center">
<label className="form-label me-2 mb-0 fw-semibold">Status :</label>
@ -94,47 +125,107 @@ const ViewExpense = ({ ExpenseId }) => {
</div>
</div>
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold">Pre-Approved :</label>
<div className="text-muted">{ExpenseId.preApproved ? "Yes" : "No"}</div>
<label className="form-label me-2 mb-0 fw-semibold">
Pre-Approved :
</label>
<div className="text-muted">{data.preApproved ? "Yes" : "No"}</div>
</div>
</div>
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold">Project :</label>
<div className="text-muted text-start">{ExpenseId.project.name}</div>
<label className="form-label me-2 mb-0 fw-semibold">
Project :
</label>
<div className="text-muted text-start">{data.project.name}</div>
</div>
</div>
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold">Created By :</label>
<label className="form-label me-2 mb-0 fw-semibold">
Created By :
</label>
<div className="text-muted">
{ExpenseId.createdBy.firstName} {ExpenseId.createdBy.lastName}
{data.createdBy.firstName} {data.createdBy.lastName}
</div>
</div>
</div>
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="col-12 col-sm-6 col-md-4 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold">Created At :</label>
<div className="text-muted">{formatUTCToLocalTime(ExpenseId.createdAt, true)}</div>
<label className="form-label me-2 mb-0 fw-semibold">
Created At :
</label>
<div className="text-muted">
{formatUTCToLocalTime(data.createdAt, true)}
</div>
</div>
</div>
</div>
<div className="text-start">
<label className="form-label me-2 mb-0 fw-semibold">Description :</label>
<div className="text-muted">
Local travel reimbursement for delivery of materials to client site via City Taxi Service
</div>
<label className="form-label me-2 mb-0 fw-semibold">Description:</label>
<div className="text-muted">{data.description}</div>
</div>
<div className="col-12 my-2 text-start ">
<label className="form-label me-2 mb-0 fw-semibold">
Attachement :
</label>
{data.documents?.map((doc) => (
<a
className="list-group-item list-group-item-action d-flex align-items-center"
key={doc.id}
>
<div
className="rounded me-3 d-flex align-items-center justify-content-center"
style={{ height: "50px", width: "80px", position: "relative" }}
>
{doc.contentType === "application/pdf" ? (
<i className="bx bxs-file-pdf"></i>
) : (
<>
{!imageLoaded[doc.id] && (
<div className="position-absolute text-secondary">
Loading...
</div>
)}
<img
src={doc.thumbPreSignedUrl}
alt={doc.fileName}
className="img-fluid rounded"
style={{
maxHeight: "100%",
maxWidth: "100%",
objectFit: "cover",
opacity: imageLoaded[doc.id] ? 1 : 0,
transition: "opacity 0.3s ease-in-out",
}}
onLoad={() => handleImageLoad(doc.id)}
onClick={() =>
setDocumentView({ IsOpen: true, Image: doc.preSignedUrl })
}
/>
</>
)}
</div>
<div className="w-100">
<p className="mb-0">{doc.fileName}</p>
<div className="d">
<i className="bx bx-cloud-download cursor-pointer"></i>
</div>
</div>
</a>
))}
</div>
<hr className="divider my-1" />
{Array.isArray(ExpenseId.nextStatus) && ExpenseId.nextStatus.length > 0 && (
{Array.isArray(data.nextStatus) && data.nextStatus.length > 0 && (
<div className="col-12 mb-3 text-start">
<label className="form-label me-2 mb-0 fw-semibold">Comment:</label>
<textarea
@ -149,7 +240,7 @@ const ViewExpense = ({ ExpenseId }) => {
<input type="hidden" {...register("selectedStatus")} />
<div className="d-flex flex-wrap gap-2 my-2">
{ExpenseId.nextStatus.map((status, index) => (
{data.nextStatus.map((status, index) => (
<button
key={index}
type="button"

View File

@ -68,6 +68,21 @@ useEffect(() => {
backgroundColor: 'rgba(0,0,0,0.3)',
};
const getPositionClass = (type) => {
switch (type) {
case 'top':
return 'mt-3';
case 'bottom':
return 'mt-auto mb-0 d-flex flex-column';
case 'left':
return 'position-absolute top-50 start-0 translate-middle-y me-auto';
case 'right':
return 'position-absolute top-50 end-0 translate-middle-y ms-auto';
case 'center':
default:
return 'modal-dialog-centered';
}
};
return (
<div
@ -80,7 +95,7 @@ useEffect(() => {
{...dataAttributesProps}
style={backdropStyle}
>
<div className={`modal-dialog ${dialogClass} ${modalSizeClass } mx-sm-auto mx-1`} role={role} >
<div className={`modal-dialog ${modalSizeClass} ${getPositionClass(modalType)} ${dialogClass} mx-sm-auto mx-1`} role={role}>
<div className="modal-content">
<div className="modal-header p-0">
{/* Close button inside the modal header */}

View File

@ -6,7 +6,7 @@ import { queryClient } from "../layouts/AuthLayout";
// -------------------Query------------------------------------------------------
export const useExpenseList = (pageSize, pageNumber, filter) => {
return useQuery({
queryKey: ["expenses", pageNumber, pageSize, filter],
queryKey: ["Expenses", pageNumber, pageSize, filter],
queryFn: async () =>
await ExpenseRepository.GetExpenseList(pageSize, pageNumber, filter).then(
(res) => res.data
@ -16,12 +16,18 @@ 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),
queryFn: async () => await ExpenseRepository.GetExpenseDetails(ExpenseId).then(
(res) => res.data
),
enabled: !!ExpenseId,
});
};
// ---------------------------Mutation---------------------------------------------
export const useCreateExpnse = (onSuccessCallBack) => {