fixed Expense type to Expense Category

This commit is contained in:
pramod.mahajan 2025-11-04 13:01:51 +05:30
parent 192c04fb6f
commit 57d33ab817
8 changed files with 472 additions and 367 deletions

View File

@ -13,7 +13,7 @@ export const ExpenseSchema = (expenseTypes) => {
return z return z
.object({ .object({
projectId: z.string().min(1, { message: "Project is required" }), projectId: z.string().min(1, { message: "Project is required" }),
expensesTypeId: z expensesCategoryId: z
.string() .string()
.min(1, { message: "Expense type is required" }), .min(1, { message: "Expense type is required" }),
paymentModeId: z.string().min(1, { message: "Payment mode is required" }), paymentModeId: z.string().min(1, { message: "Payment mode is required" }),
@ -66,7 +66,7 @@ export const ExpenseSchema = (expenseTypes) => {
) )
.superRefine((data, ctx) => { .superRefine((data, ctx) => {
const expenseType = expenseTypes.find( const expenseType = expenseTypes.find(
(et) => et.id === data.expensesTypeId (et) => et.id === data.expensesCategoryId
); );
if ( if (
expenseType?.noOfPersonsRequired && expenseType?.noOfPersonsRequired &&
@ -83,7 +83,7 @@ export const ExpenseSchema = (expenseTypes) => {
export const defaultExpense = { export const defaultExpense = {
projectId: "", projectId: "",
expensesTypeId: "", expensesCategoryId: "",
paymentModeId: "", paymentModeId: "",
paidById: "", paidById: "",
transactionDate: "", transactionDate: "",

View File

@ -1,19 +1,41 @@
import { useState,useMemo } from "react"; import { useState, useMemo } from "react";
import Avatar from "../common/Avatar"; import Avatar from "../common/Avatar";
import { formatUTCToLocalTime } from "../../utils/dateUtils"; import { formatUTCToLocalTime } from "../../utils/dateUtils";
import Timeline from "../common/TimeLine";
const ExpenseStatusLogs = ({ data }) => { const ExpenseStatusLogs = ({ data }) => {
const [visibleCount, setVisibleCount] = useState(4); const [visibleCount, setVisibleCount] = useState(4);
const sortedLogs = useMemo(() => { const sortedLogs = useMemo(() => {
if (!data?.updateLogs) return []; if (!data?.updateLogs) return [];
return [...data.updateLogs].sort( return [...data.updateLogs].sort(
(a, b) => new Date(b.updateAt) - new Date(a.updateAt) (a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)
); );
}, [data?.updateLogs]); }, [data?.updateLogs]);
const logsToShow = sortedLogs.slice(0, visibleCount); const logsToShow = useMemo(
() => sortedLogs.slice(0, visibleCount),
[sortedLogs, visibleCount]
);
const timelineData = useMemo(() => {
return logsToShow.map((log, index) => ({
id: index + 1,
title: log.nextStatus?.name || "Status Updated",
description: log.nextStatus?.description || "",
timeAgo: formatTimeAgo(log.updatedAt),
color: log.nextStatus?.color || "primary",
users: log.updatedBy
? [
{
name: `${log.updatedBy.firstName || ""} ${` log?.updatedBy?.lastName` || ""}`.trim(),
role: log.updatedBy.jobRoleName || "",
avatar: log.updatedBy.photo || "assets/img/avatars/default.png",
},
]
: [],
}));
}, [logsToShow])
const handleShowMore = () => { const handleShowMore = () => {
setVisibleCount((prev) => prev + 4); setVisibleCount((prev) => prev + 4);
@ -37,8 +59,8 @@ const ExpenseStatusLogs = ({ data }) => {
<small className="text-secondary text-tiny ms-2"> <small className="text-secondary text-tiny ms-2">
<em>{log.action}</em> <em>{log.action}</em>
</small> </small>
<span className="text-tiny text-secondary d-block" > <span className="text-tiny text-secondary d-block">
{formatUTCToLocalTime(log.updateAt,true)} {formatUTCToLocalTime(log.updateAt, true)}
</span> </span>
</div> </div>
<div className="d-flex align-items-center text-muted small mt-1"> <div className="d-flex align-items-center text-muted small mt-1">
@ -60,9 +82,12 @@ const ExpenseStatusLogs = ({ data }) => {
</button> </button>
</div> </div>
)} )}
<Timeline items={timelineData} />
</> </>
); );
}; };
export default ExpenseStatusLogs; export default ExpenseStatusLogs;

View File

@ -40,11 +40,11 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
const [ExpenseType, setExpenseType] = useState(); const [ExpenseType, setExpenseType] = useState();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { const {
ExpenseTypes, ExpenseCategories,
loading: ExpenseLoading, loading: ExpenseLoading,
error: ExpenseError, error: ExpenseError,
} = useExpenseCategory; } = useExpenseCategory();
const schema = ExpenseSchema(ExpenseTypes); const schema = ExpenseSchema(ExpenseCategories);
const { const {
register, register,
handleSubmit, handleSubmit,
@ -148,7 +148,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
if (expenseToEdit && data) { if (expenseToEdit && data) {
reset({ reset({
projectId: data.project.id || "", projectId: data.project.id || "",
expensesTypeId: data.expensesType.id || "", expensesCategoryId: data.expensesType.id || "",
paymentModeId: data.paymentMode.id || "", paymentModeId: data.paymentMode.id || "",
paidById: data.paidBy.id || "", paidById: data.paidBy.id || "",
transactionDate: data.transactionDate?.slice(0, 10) || "", transactionDate: data.transactionDate?.slice(0, 10) || "",
@ -194,10 +194,10 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
CreateExpense(payload); CreateExpense(payload);
} }
}; };
const ExpenseTypeId = watch("expensesTypeId"); const ExpenseTypeId = watch("expensesCategoryId");
useEffect(() => { useEffect(() => {
setExpenseType(ExpenseTypes?.find((type) => type.id === ExpenseTypeId)); setExpenseType(ExpenseCategories?.find((type) => type.id === ExpenseTypeId));
}, [ExpenseTypeId]); }, [ExpenseTypeId]);
const handleClose = () => { const handleClose = () => {
@ -239,13 +239,13 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
</div> </div>
<div className="col-md-6"> <div className="col-md-6">
<Label htmlFor="expensesTypeId" className="form-label" required> <Label htmlFor="expensesCategoryId" className="form-label" required>
Expense Type Expense Category
</Label> </Label>
<select <select
className="form-select form-select-sm" className="form-select form-select-sm"
id="expensesTypeId" id="expensesCategoryId"
{...register("expensesTypeId")} {...register("expensesCategoryId")}
> >
<option value="" disabled> <option value="" disabled>
Select Type Select Type
@ -253,16 +253,16 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
{ExpenseLoading ? ( {ExpenseLoading ? (
<option disabled>Loading...</option> <option disabled>Loading...</option>
) : ( ) : (
ExpenseTypes?.map((expense) => ( ExpenseCategories?.map((expense) => (
<option key={expense.id} value={expense.id}> <option key={expense.id} value={expense.id}>
{expense.name} {expense.name}
</option> </option>
)) ))
)} )}
</select> </select>
{errors.expensesTypeId && ( {errors.expensesCategoryId && (
<small className="danger-text"> <small className="danger-text">
{errors.expensesTypeId.message} {errors.expensesCategoryId.message}
</small> </small>
)} )}
</div> </div>

View File

@ -21,12 +21,12 @@ function ManagePaymentRequest({ closeModal, requestToEdit = null }) {
const { const {
ExpenseTypes, ExpenseCategories,
loading: ExpenseLoading, loading: ExpenseLoading,
error: ExpenseError, error: ExpenseError,
} = useExpenseCategory(); } = useExpenseCategory();
const schema = PaymentRequestSchema(ExpenseTypes); const schema = PaymentRequestSchema(ExpenseCategories);
const { register, control, watch, handleSubmit, setValue, reset, formState: { errors }, } = useForm({ const { register, control, watch, handleSubmit, setValue, reset, formState: { errors }, } = useForm({
resolver: zodResolver(schema), resolver: zodResolver(schema),
defaultValues: defaultPaymentRequest, defaultValues: defaultPaymentRequest,
@ -193,12 +193,12 @@ function ManagePaymentRequest({ closeModal, requestToEdit = null }) {
{...register("expenseCategoryId")} {...register("expenseCategoryId")}
> >
<option value="" disabled> <option value="" disabled>
Select Type Select Category
</option> </option>
{ExpenseLoading ? ( {ExpenseLoading ? (
<option disabled>Loading...</option> <option disabled>Loading...</option>
) : ( ) : (
ExpenseTypes?.map((expense) => ( ExpenseCategories?.map((expense) => (
<option key={expense.id} value={expense.id}> <option key={expense.id} value={expense.id}>
{expense.name} {expense.name}
</option> </option>

View File

@ -112,81 +112,80 @@ const ViewPaymentRequest = ({ requestId }) => {
return ( return (
<form className="container px-3" onSubmit={handleSubmit(onSubmit)}> <form className="container px-3" onSubmit={handleSubmit(onSubmit)}>
<div className="col-12 mb-1">
<h5 className="fw-semibold m-0">Payment Request Details</h5>
<hr />
</div>
<div className="row mb-1"> <div className="row mb-1">
<div className="col-12 mb-1"> <div className="col-12 col-sm-6 col-md-8">
<h5 className="fw-semibold m-0">Request Details</h5> <div className="col-12 text-start fw-semibold my-2">
<hr /> {data?.paymentRequestUID}
</div> </div>
<div className="col-12 text-start fw-semibold my-2"> {/* Row 1 */}
{data?.paymentRequestUID} <div className="col-md-6 mb-3">
</div> <div className="d-flex">
{/* Row 1 */} <label
<div className="col-md-6 mb-3"> className="form-label me-2 mb-0 fw-semibold text-start"
<div className="d-flex"> style={{ minWidth: "130px" }}
<label >
className="form-label me-2 mb-0 fw-semibold text-start" Project Name :
style={{ minWidth: "130px" }} </label>
> <div className="text-muted">{data.project.name}</div>
Project Name :
</label>
<div className="text-muted">
{data.project.name}
</div> </div>
</div> </div>
</div> <div className="col-md-6 mb-3">
<div className="col-md-6 mb-3"> <div className="d-flex">
<div className="d-flex"> <label
<label className="form-label me-2 mb-0 fw-semibold text-start"
className="form-label me-2 mb-0 fw-semibold text-start" style={{ minWidth: "130px" }}
style={{ minWidth: "130px" }} >
> Due Date :
Due Date : </label>
</label> <div className="text-muted">
<div className="text-muted"> {formatUTCToLocalTime(data?.dueDate)}
{formatUTCToLocalTime(data?.dueDate)} </div>
</div> </div>
</div> </div>
</div> <div className="col-md-6 mb-3">
<div className="col-md-6 mb-3"> <div className="d-flex">
<div className="d-flex"> <label
<label className="form-label me-2 mb-0 fw-semibold text-start"
className="form-label me-2 mb-0 fw-semibold text-start" style={{ minWidth: "130px" }}
style={{ minWidth: "130px" }} >
> Expense Category :
Expense Category : </label>
</label> <div className="text-muted">{data?.expenseCategory?.name}</div>
<div className="text-muted">{data?.expenseCategory?.name}</div> </div>
</div> </div>
</div>
{/* Row 2 */} {/* Row 2 */}
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<div className="d-flex"> <div className="d-flex">
<label <label
className="form-label me-2 mb-0 fw-semibold text-start" className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
Supplier : Supplier :
</label> </label>
<div className="text-muted">{data?.payee}</div> <div className="text-muted">{data?.payee}</div>
</div> </div>
</div> </div>
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<div className="d-flex"> <div className="d-flex">
<label <label
className="form-label me-2 mb-0 fw-semibold text-start" className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
Amount : Amount :
</label> </label>
<div className="text-muted"> <div className="text-muted">
{formatCurrency(data?.amount, data?.currency?.currencyCode)} {formatCurrency(data?.amount, data?.currency?.currencyCode)}
</div>
</div> </div>
</div> </div>
</div>
{/* Row 3 */} {/* Row 3 */}
{/* <div className="col-md-6 mb-3"> {/* <div className="col-md-6 mb-3">
<div className="d-flex"> <div className="d-flex">
<label <label
className="form-label me-2 mb-0 fw-semibold text-start" className="form-label me-2 mb-0 fw-semibold text-start"
@ -197,298 +196,306 @@ const ViewPaymentRequest = ({ requestId }) => {
<div className="text-muted">{data?.paymentMode?.name}</div> <div className="text-muted">{data?.paymentMode?.name}</div>
</div> </div>
</div> */} </div> */}
{data?.gstNumber && ( {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>
)}
{/* Row 4 */}
<div className="col-md-6 mb-3"> <div className="col-md-6 mb-3">
<div className="d-flex"> <div className="d-flex">
<label <label
className="form-label me-2 mb-0 fw-semibold text-start" className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
GST Number : Status :
</label> </label>
<div className="text-muted">{data?.gstNumber}</div> <span
className={`badge bg-label-${
getColorNameFromHex(data?.expenseStatus?.color) || "secondary"
}`}
>
{data?.expenseStatus?.name}
</span>
</div> </div>
</div> </div>
)} <div className="col-md-6 mb-3">
<div className="d-flex">
{/* 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?.expenseStatus?.color) || "secondary"
}`}
>
{data?.expenseStatus?.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" }}
>
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 6 */}
{data?.createdBy && (
<div className="col-md-6 text-start">
<div className="d-flex align-items-center">
<label <label
className="form-label me-2 mb-0 fw-semibold" className="form-label me-2 mb-0 fw-semibold text-start"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
Created By : Pre-Approved :
</label> </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" }}
>
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 6 */}
{data?.createdBy && (
<div className="col-md-6 text-start">
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<Avatar <label
size="xs" className="form-label me-2 mb-0 fw-semibold"
classAvatar="m-0" style={{ minWidth: "130px" }}
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>
)}
{data?.paidBy && (
<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="text-start my-1">
<label className="fw-semibold form-label">Description : </label>
<div className="text-muted">{data?.description}</div>
</div>
</div>
<div className="col-12 text-start">
<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="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} Created By :
</small> </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>
})} )}
</div>
</div>
{data?.paidTransactionId && (
<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?.paidTransactionId }
</div>
<div className="col-md-6 ">
<label className="form-label me-2 mb-0 fw-semibold">
Transaction Date :
</label>
{formatUTCToLocalTime(data?.paidAt)}
</div>
{data?.paidBy && ( {data?.paidBy && (
<> <div className="col-md-6 text-start">
<div className="col-md-6 d-flex align-items-center"> <div className="d-flex align-items-center">
<label className="form-label me-2 mb-0 fw-semibold"> <label
className="form-label me-2 mb-0 fw-semibold"
style={{ minWidth: "130px" }}
>
Paid By : Paid By :
</label> </label>
<Avatar <div className="d-flex align-items-center ">
size="xs" <Avatar
classAvatar="m-0 me-1" size="xs"
firstName={data?.paidBy?.firstName} classAvatar="m-0"
lastName={data?.paidBy?.lastName} firstName={data?.paidBy?.firstName}
/> lastName={data?.paidBy?.lastName}
<span className="text-muted"> />
{`${data?.paidBy?.firstName} ${data?.paidBy?.lastName}`.trim()} <span className="text-muted">
</span> {`${data?.paidBy?.firstName ?? ""} ${
data?.paidBy?.lastName ?? ""
}`.trim() || "N/A"}
</span>
</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>
<div className="col-6 text-start">
<label className="form-label me-2 mb-2 fw-semibold">
Attachment :
</label>
<div className="d-flex flex-wrap gap-2">
{data?.documents?.length > 0 ? (
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>
);
})
) : (
<p className="m-0">No Attachment</p>
)}
</div>
</div>
{data?.paidTransactionId && (
<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?.paidTransactionId}
</div>
<div className="col-md-6 ">
<label className="form-label me-2 mb-0 fw-semibold">
Transaction Date :
</label>
{formatUTCToLocalTime(data?.paidAt)}
</div>
{data?.paidBy && (
<>
<div className="col-md-6 d-flex align-items-center">
<label className="form-label me-2 mb-0 fw-semibold">
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()}
</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("paidTransactionId")}
/>
{errors.paidTransactionId && (
<small className="danger-text">
{errors.paidTransactionId.message}
</small>
)}
</div>
<div className="col-12 col-md-6 text-start">
<label className="form-label">Transaction Date </label>
<DatePicker
name="paidAt"
control={control}
minDate={data?.createdAt}
maxDate={new Date()}
/>
{errors.paidAt && (
<small className="danger-text">
{errors.paidAt.message}
</small>
)}
</div>
<div className="col-12 col-md-6 text-start">
<label className="form-label">Paid By </label>
<EmployeeSearchInput
control={control}
name="paidById"
projectId={null}
/>
</div>
</div>
)}
<div className="col-12 mb-3 text-start">
{((nextStatusWithPermission.length > 0 && !isRejectedRequest) ||
(isRejectedRequest && 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 &&
(!isRejectedRequest || 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>
</> </>
)} )}
</div> </div>
)} <div className="col-12 col-sm-6 col-md-4">
<hr className="divider my-1 border-2 divider-primary my-2" /> <ExpenseStatusLogs data={data} />
</div>
{Array.isArray(data?.nextStatus) && data?.nextStatus.length > 0 && ( </div>
<>
{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("paidTransactionId")}
/>
{errors.paidTransactionId && (
<small className="danger-text">
{errors.paidTransactionId.message}
</small>
)}
</div>
<div className="col-12 col-md-6 text-start">
<label className="form-label">Transaction Date </label>
<DatePicker
name="paidAt"
control={control}
minDate={data?.createdAt}
maxDate={new Date()}
/>
{errors.paidAt && (
<small className="danger-text">
{errors.paidAt.message}
</small>
)}
</div>
<div className="col-12 col-md-6 text-start">
<label className="form-label">Paid By </label>
<EmployeeSearchInput
control={control}
name="paidById"
projectId={null}
/>
</div>
</div>
)}
<div className="col-12 mb-3 text-start">
{((nextStatusWithPermission.length > 0 && !isRejectedRequest) ||
(isRejectedRequest && 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 &&
(!isRejectedRequest || 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>
</>
)}
<ExpenseStatusLogs data={data} />
</form> </form>
); );
}; };

View File

@ -0,0 +1,73 @@
import React from "react";
const Timeline = ({ items = [], transparent = true }) => {
return (
<ul className={`timeline ${transparent ? "timeline-transparent text-start" : ""}`}>
{items.map((item) => (
<li
key={item.id}
className={`timeline-item ${transparent ? "timeline-item-transparent" : ""}`}
>
<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">{item.timeAgo}</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>
)}
{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="avatar me-1" title={user.name}>
{user.avatar ? (
<img
src={user.avatar}
alt={user.name}
className="rounded-circle"
width="32"
height="32"
/>
) : (
<span className="avatar-initial rounded-circle pull-up bg-light">
{user.name}
</span>
)}
</li>
))}
</ul>
{item.users[0]?.role && (
<div className="ms-2">
<p className="mb-0 small fw-medium">{item.users[0].name}</p>
<small>{item.users[0].role}</small>
</div>
)}
</div>
)}
</div>
</li>
))}
</ul>
);
};
export default Timeline;

View File

@ -152,13 +152,13 @@ export const useContactTags = () => {
export const useExpenseCategory = () => { export const useExpenseCategory = () => {
const { const {
data: ExpenseTypes = [], data: ExpenseCategories = [],
isLoading: loading, isLoading: loading,
error, error,
} = useQuery({ } = useQuery({
queryKey: ["Expense Category"], queryKey: ["Expense Category"],
queryFn: async () => { queryFn: async () => {
const res = await MasterRespository.getExpenseCategory() const res = await MasterRespository.getExpenseCategory();
return res.data; return res.data;
}, },
onError: (error) => { onError: (error) => {
@ -171,7 +171,7 @@ export const useExpenseCategory = () => {
}, },
}); });
return { ExpenseTypes, loading, error }; return { ExpenseCategories, loading, error };
}; };
export const usePaymentMode = () => { export const usePaymentMode = () => {
const { const {

View File

@ -120,11 +120,11 @@ const PaymentRequestPage = () => {
{ViewRequest.view && ( {ViewRequest.view && (
<GlobalModel <GlobalModel
isOpen isOpen
size="lg" size="xl"
modalType="top" modalType="top"
closeModal={() => setVieRequest({ requestId: null, view: false })} closeModal={() => setVieRequest({ requestId: null, view: false })}
> >
<ViewPaymentRequest requestId={ViewRequest?.requestId}/> <ViewPaymentRequest requestId={ViewRequest?.requestId}/>
</GlobalModel> </GlobalModel>
)} )}