added layout for Expense veiw modal

This commit is contained in:
pramod.mahajan 2025-11-04 23:17:43 +05:30
parent f688a7169e
commit e8698473db
2 changed files with 260 additions and 434 deletions

View File

@ -111,380 +111,200 @@ const ViewExpense = ({ ExpenseId }) => {
return (
<form className="container px-3" onSubmit={handleSubmit(onSubmit)}>
<div className="col-12 mb-1">
<h5 className="fw-semibold m-0">Expense Details</h5>
</div>
<div className="row mb-1">
<div className="col-12 col-md-7">
<div className="row">
<div className="col-12 text-start fw-semibold my-2">
{data?.expenseUId}
</div>
{/* Row 1 */}
<div className="col-md-6 mb-3">
<div className="d-flex">
<label
className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }}
>
Transaction Date :
</label>
<div className="text-muted">
{formatUTCToLocalTime(data?.transactionDate)}
</div>
</div>
</div>
<div className="col-md-6 mb-3">
<div className="d-flex">
<label
className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }}
>
Expense Type :
</label>
<div className="text-muted">{data?.expensesType?.name}</div>
</div>
</div>
<div className="col-12 mb-1">
<h5 className="fw-semibold m-0">Expense Details</h5>
</div>
{/* Row 2 */}
<div className="col-md-6 mb-3">
<div className="d-flex">
<label
className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }}
>
Supplier :
</label>
<div className="text-muted">{data?.supplerName}</div>
</div>
</div>
<div className="col-md-6 mb-3">
<div className="d-flex">
<label
className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }}
>
Amount :
</label>
<div className="text-muted"> {data.amount}</div>
</div>
</div>
<div className="row mb-1 border-top border-2 border-light-subtle">
<div className="col-12 col-lg-7 col-xl-8 border-end border-2 border-light-subtle mb-3">
<div className="row">
<div className="col-12 d-flex justify-content-between text-start fw-semibold my-2">
<span>{data?.expenseUId}</span>
<span
className={`badge bg-label-${
getColorNameFromHex(data?.status?.color) || "secondary"
}`}t
>
{data?.status?.name}
</span>
</div>
{/* Row 3 */}
<div className="col-md-6 mb-3">
<div className="d-flex">
<label
className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }}
>
Payment Mode :
</label>
<div className="text-muted">{data?.paymentMode?.name}</div>
</div>
{/* Row 1 */}
<div className="col-md-6 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold text-start" style={{ minWidth: "130px" }}>
Transaction Date :
</label>
<div className="text-muted">
{formatUTCToLocalTime(data?.transactionDate)}
</div>
{data?.gstNumber && (
<div className="col-md-6 mb-3">
<div className="d-flex">
<label
className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }}
>
GST Number :
</label>
<div className="text-muted">{data?.gstNumber}</div>
</div>
</div>
)}
</div>
</div>
{/* Row 4 */}
<div className="col-md-6 mb-3">
<div className="d-flex">
<label
className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }}
>
Status :
</label>
<span
className={`badge bg-label-${
getColorNameFromHex(data?.status?.color) || "secondary"
}`}
>
{data?.status?.name}
</span>
</div>
</div>
<div className="col-md-6 mb-3">
<div className="d-flex">
<label
className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }}
>
Pre-Approved :
</label>
<div className="text-muted">
{data.preApproved ? "Yes" : "No"}
</div>
</div>
</div>
<div className="col-md-6 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold text-start" style={{ minWidth: "130px" }}>
Expense Type :
</label>
<div className="text-muted">{data?.expensesType?.name}</div>
</div>
</div>
<div className="col-md-6 mb-3">
<div className="d-flex">
<label
className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }}
>
Project :
</label>
<div className="text-muted">{data?.project?.name}</div>
</div>
</div>
<div className="col-md-6 mb-3">
<div className="d-flex">
<label
className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }}
>
Created At :
</label>
<div className="text-muted">
{formatUTCToLocalTime(data?.createdAt, true)}
</div>
</div>
</div>
{/* Row 2 */}
<div className="col-md-6 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold text-start" style={{ minWidth: "130px" }}>
Supplier :
</label>
<div className="text-muted">{data?.supplerName}</div>
</div>
</div>
{/* Row 6 */}
{data.createdBy && (
<div className="col-md-6 text-start">
<div className="d-flex align-items-center">
<label
className="form-label me-2 mb-0 fw-semibold"
style={{ minWidth: "130px" }}
>
Created By :
</label>
<div className="d-flex align-items-center">
<Avatar
size="xs"
classAvatar="m-0"
firstName={data.createdBy?.firstName}
lastName={data.createdBy?.lastName}
/>
<span className="text-muted">
{`${data.createdBy?.firstName ?? ""} ${
data.createdBy?.lastName ?? ""
}`.trim() || "N/A"}
</span>
</div>
</div>
</div>
)}
<div className="col-md-6 text-start">
<div className="d-flex align-items-center">
<label
className="form-label me-2 mb-0 fw-semibold"
style={{ minWidth: "130px" }}
>
Paid By :
</label>
<div className="d-flex align-items-center ">
<Avatar
size="xs"
classAvatar="m-0"
firstName={data.paidBy?.firstName}
lastName={data.paidBy?.lastName}
/>
<span className="text-muted">
{`${data.paidBy?.firstName ?? ""} ${
data.paidBy?.lastName ?? ""
}`.trim() || "N/A"}
</span>
</div>
</div>
</div>
<div className="col-md-6 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold text-start" style={{ minWidth: "130px" }}>
Amount :
</label>
<div className="text-muted"> {data.amount}</div>
</div>
</div>
<div className="text-start my-1">
<label className="fw-semibold form-label">Description : </label>
<div className="text-muted">{data?.description}</div>
</div>
{/* Row 3 */}
<div className="col-md-6 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold text-start" style={{ minWidth: "130px" }}>
Payment Mode :
</label>
<div className="text-muted">{data?.paymentMode?.name}</div>
</div>
</div>
<div className="col-12 text-start">
<label className="form-label me-2 mb-2 fw-semibold">
Attachment :
{data?.gstNumber && (
<div className="col-md-6 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold text-start" style={{ minWidth: "130px" }}>
GST Number :
</label>
<div className="d-flex flex-wrap gap-2">
{data?.documents?.map((doc) => {
const isImage = doc.contentType?.includes("image");
return (
<div
key={doc.documentId}
className="border rounded hover-scale p-2 d-flex flex-column align-items-center"
style={{
width: "80px",
cursor: isImage ? "pointer" : "default",
}}
onClick={() => {
if (isImage) {
setDocumentView({
IsOpen: true,
Image: doc.preSignedUrl,
});
}
}}
>
<i
className={`bx ${getIconByFileType(doc.contentType)}`}
style={{ fontSize: "30px" }}
></i>
<small
className="text-center text-tiny text-truncate w-100"
title={doc.fileName}
>
{doc.fileName}
</small>
</div>
);
})}
</div>
<div className="text-muted">{data?.gstNumber}</div>
</div>
</div>
)}
{data.expensesReimburse && (
<div className="row text-start mt-2">
<div className="col-md-6 mb-sm-0 mb-2">
<label className="form-label me-2 mb-0 fw-semibold">
Transaction ID :
</label>
{data.expensesReimburse.reimburseTransactionId || "N/A"}
</div>
<div className="col-md-6 ">
<label className="form-label me-2 mb-0 fw-semibold">
Reimburse Date :
</label>
{formatUTCToLocalTime(data.expensesReimburse.reimburseDate)}
</div>
{data.expensesReimburse && (
<>
<div className="col-md-6 d-flex align-items-center">
<label className="form-label me-2 mb-0 fw-semibold">
Reimburse By :
</label>
<Avatar
size="xs"
classAvatar="m-0 me-1"
firstName={
data?.expensesReimburse?.reimburseBy?.firstName
}
lastName={
data?.expensesReimburse?.reimburseBy?.lastName
}
/>
<span className="text-muted">
{`${data?.expensesReimburse?.reimburseBy?.firstName} ${data?.expensesReimburse?.reimburseBy?.lastName}`.trim()}
</span>
</div>
</>
)}
</div>
)}
{Array.isArray(data?.nextStatus) && data.nextStatus.length > 0 && (
<>
{IsPaymentProcess && nextStatusWithPermission?.length > 0 && (
<div className="row">
<div className="col-12 col-md-6 text-start">
<label className="form-label">Transaction Id </label>
<input
type="text"
className="form-control form-control-sm"
{...register("reimburseTransactionId")}
/>
{errors.reimburseTransactionId && (
<small className="danger-text">
{errors.reimburseTransactionId.message}
</small>
)}
</div>
<div className="col-12 col-md-6 text-start">
<label className="form-label">Transaction Date </label>
<DatePicker
name="reimburseDate"
control={control}
minDate={data?.createdAt}
maxDate={new Date()}
/>
{errors.reimburseDate && (
<small className="danger-text">
{errors.reimburseDate.message}
</small>
)}
</div>
<div className="col-12 col-md-6 text-start">
<label className="form-label">Reimburse By </label>
<EmployeeSearchInput
control={control}
name="reimburseById"
projectId={null}
/>
</div>
</div>
)}
<div className="col-12 mb-3 text-start">
{((nextStatusWithPermission.length > 0 &&
!IsRejectedExpense) ||
(IsRejectedExpense && isCreatedBy)) && (
<>
<Label className="form-label me-2 mb-0" required>
Comment
</Label>
<textarea
className="form-control form-control-sm"
{...register("comment")}
rows="2"
/>
{errors.comment && (
<small className="danger-text">
{errors.comment.message}
</small>
)}
</>
)}
{nextStatusWithPermission?.length > 0 &&
(!IsRejectedExpense || isCreatedBy) && (
<div className="text-end flex-wrap gap-2 my-2 mt-3">
{nextStatusWithPermission.map((status, index) => (
<button
key={status.id || index}
type="button"
onClick={() => {
setClickedStatusId(status.id);
setValue("statusId", status.id);
handleSubmit(onSubmit)();
}}
disabled={isPending || isFetching}
className="btn btn-primary btn-sm cursor-pointer mx-2 border-0"
>
{isPending && clickedStatusId === status.id
? "Please Wait..."
: status.displayName || status.name}
</button>
))}
</div>
)}
</div>
</>
)}
<div className="col-md-6 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold text-start" style={{ minWidth: "130px" }}>
Pre-Approved :
</label>
<div className="text-muted">{data.preApproved ? "Yes" : "No"}</div>
</div>
</div>
<div className="col-12 col-md-5">
<div className="row">
<ExpenseStatusLogs data={data} />
{/* Row 5 */}
<div className="col-md-6 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold text-start" style={{ minWidth: "130px" }}>
Project :
</label>
<div className="text-muted">{data?.project?.name}</div>
</div>
</div>
<div className="col-md-6 mb-3">
<div className="d-flex">
<label className="form-label me-2 mb-0 fw-semibold text-start" style={{ minWidth: "130px" }}>
Created At :
</label>
<div className="text-muted">{formatUTCToLocalTime(data?.createdAt, true)}</div>
</div>
</div>
{/* Created & Paid By */}
{data.createdBy && (
<div className="col-md-6 text-start mb-3">
<div className="d-flex align-items-center">
<label className="form-label me-2 mb-0 fw-semibold" style={{ minWidth: "130px" }}>
Created By :
</label>
<Avatar
size="xs"
classAvatar="m-0 me-1"
firstName={data.createdBy?.firstName}
lastName={data.createdBy?.lastName}
/>
<span className="text-muted">
{`${data.createdBy?.firstName ?? ""} ${data.createdBy?.lastName ?? ""}`.trim() || "N/A"}
</span>
</div>
</div>
)}
<div className="col-md-6 text-start mb-3">
<div className="d-flex align-items-center">
<label className="form-label me-2 mb-0 fw-semibold" style={{ minWidth: "130px" }}>
Paid By :
</label>
<Avatar
size="xs"
classAvatar="m-0 me-1"
firstName={data.paidBy?.firstName}
lastName={data.paidBy?.lastName}
/>
<span className="text-muted">
{`${data.paidBy?.firstName ?? ""} ${data.paidBy?.lastName ?? ""}`.trim() || "N/A"}
</span>
</div>
</div>
{/* Description */}
<div className="col-12 text-start mb-2">
<label className="fw-semibold form-label">Description : </label>
<div className="text-muted">{data?.description}</div>
</div>
{/* Attachments */}
<div className="col-12 text-start mb-2">
<label className="form-label me-2 mb-2 fw-semibold">Attachment :</label>
<div className="d-flex flex-wrap gap-2">
{data?.documents?.map((doc) => {
const isImage = doc.contentType?.includes("image");
return (
<div
key={doc.documentId}
className="d-flex align-items-center cusor-pointer"
onClick={() => {
if (isImage) {
setDocumentView({
IsOpen: true,
Image: doc.preSignedUrl,
});
}
}}
>
<i className={`bx ${getIconByFileType(doc.contentType)}`} style={{ fontSize: "30px" }}></i>
<small className="text-center text-tiny text-truncate w-100" title={doc.fileName}>
{doc.fileName}
</small>
</div>
);
})}
</div>
</div>
{/* ... your remaining conditional sections */}
</div>
</form>
</div>
<div className="col-12 col-lg-5 col-xl-4">
<div className="d-flex align-items-center text-secondary my-2">
<i className='bx bx-time-five me-2'></i> <p className=" m-0">TimeLine</p>
</div>
<ExpenseStatusLogs data={data} />
</div>
</div>
</form>
);
};

View File

@ -11,93 +11,99 @@ const Timeline = ({ items = [], transparent = true }) => {
transparent ? "timeline-transparent text-start" : ""
}`}
>
{items && items?.map((item) => (
<li
key={item.id}
className={`timeline-item ${
transparent ? "timeline-item-transparent" : ""
}`}
>
<span
className={`timeline-point timeline-point-${
item.color || "primary"
{items &&
items?.map((item) => (
<li
key={item.id}
className={`timeline-item ${
transparent ? "timeline-item-transparent" : ""
}`}
></span>
>
<span
className={`timeline-point timeline-point-${
item.color || "primary"
}`}
></span>
<div className="timeline-event">
<div className="timeline-header mb-3 d-flex justify-content-between">
<h6 className="mb-0 text-body">{item.title}</h6>
<small className="text-body-secondary"><Tooltip text={formatUTCToLocalTime(item.timeAgo,true)}>{moment.utc(item.timeAgo).local().fromNow()}</Tooltip></small>
</div>
{item.description && <p className="mb-2">{item.description}</p>}
{item.attachments && item.attachments.length > 0 && (
<div className="d-flex align-items-center mb-2">
{item.attachments.map((att, i) => (
<div
key={i}
className="badge bg-lighter rounded d-flex align-items-center gap-2 p-2"
>
{att.icon && (
<img
src={att.icon}
alt="file"
width="15"
className="me-2"
/>
)}
<span className="h6 mb-0">{att.name}</span>
</div>
))}
<div className="timeline-event">
<div className="timeline-header mb-3 d-flex justify-content-between">
<h6 className="mb-0 text-body">{item.title}</h6>
<small className="text-body-secondary">
<Tooltip text={formatUTCToLocalTime(item.timeAgo, true)}>
{moment.utc(item.timeAgo).local().fromNow()}
</Tooltip>
</small>
</div>
)}
{item.users && item.users.length > 0 && (
<div className="d-flex flex-wrap align-items-center mb-2">
<ul className="list-unstyled users-list d-flex align-items-center avatar-group m-0">
{item.users.map((user, i) => (
<li key={i} className="m-0" title={user.name}>
{user.avatar ? (
{item.description && <p className="mb-2">{item.description}</p>}
{item.attachments && item.attachments.length > 0 && (
<div className="d-flex align-items-center mb-2">
{item.attachments.map((att, i) => (
<div
key={i}
className="badge bg-lighter rounded d-flex align-items-center gap-2 p-2"
>
{att.icon && (
<img
src={user.avatar}
alt={user.name}
className="rounded-circle"
width="32"
height="32"
/>
) : (
<Avatar
size="xs"
firstName={user.firstName}
lastName={user.lastName}
src={att.icon}
alt="file"
width="15"
className="me-2"
/>
)}
</li>
<span className="h6 mb-0">{att.name}</span>
</div>
))}
</ul>
</div>
)}
{item.users?.length === 1 && (
<div className="m-0">
<p className="mb-0 text-xs fw-medium">{`${item.users[0].firstName} ${item.users[0].lastName}`}</p>
<small>{item.users[0].role}</small>
</div>
)}
</div>
)}
</div>
</li>
))}
{item.users && item.users.length > 0 && (
<div className="d-flex flex-wrap align-items-center mb-2">
<ul className="list-unstyled users-list d-flex align-items-center avatar-group m-0">
{item.users.map((user, i) => (
<li key={i} className="m-0" title={user.name}>
{user.avatar ? (
<img
src={user.avatar}
alt={user.name}
className="rounded-circle"
width="32"
height="32"
/>
) : (
<Avatar
size="xs"
firstName={user.firstName}
lastName={user.lastName}
/>
)}
</li>
))}
</ul>
{!items || items.length == 0 && (
<li
className={`timeline-item text-center ${
transparent ? "timeline-item-transparent" : ""
}`}
>
Not action yet.
</li>
)}
{item.users?.length === 1 && (
<div className="m-0">
<p className="mb-0 text-xs fw-medium">{`${item.users[0].firstName} ${item.users[0].lastName}`}</p>
<small>{item.users[0].role}</small>
</div>
)}
</div>
)}
</div>
</li>
))}
{!items ||
(items.length == 0 && (
<li
className={`timeline-item text-center ${
transparent ? "timeline-item-transparent" : ""
}`}
>
Not action yet.
</li>
))}
</ul>
);
};