fixed file uploading and payee

This commit is contained in:
pramod.mahajan 2025-11-04 17:15:42 +05:30
parent 611116b70c
commit 0dc68eb20d
11 changed files with 604 additions and 584 deletions

View File

@ -307,7 +307,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
})
}
></i>
{canDetetExpense(expense) &&
{
canEditExpense(expense) && (
<div className="dropdown z-2">
<button

View File

@ -13,7 +13,7 @@ export const ExpenseSchema = (expenseTypes) => {
return z
.object({
projectId: z.string().min(1, { message: "Project is required" }),
expensesCategoryId: z
expenseCategoryId: z
.string()
.min(1, { message: "Expense type is required" }),
paymentModeId: z.string().min(1, { message: "Payment mode is required" }),

View File

@ -12,7 +12,7 @@ const Filelist = ({ files, removeFile, expenseToEdit }) => {
return true;
})
.map((file, idx) => (
<div className="col-12 col-sm-6 col-md-4 col-lg-8 bg-white shadow-sm rounded p-2 m-2">
<div className="col-12 col-sm-6 col-md-4 col-lg-8 bg-white shadow-sm rounded p-2 m-2" key={idx}>
<div className="row align-items-center">
{/* File icon and info */}
<div className="col-10 d-flex align-items-center gap-2">

View File

@ -31,6 +31,7 @@ import Label from "../common/Label";
import EmployeeSearchInput from "../common/EmployeeSearchInput";
import Filelist from "./Filelist";
const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
const {
data,
@ -148,7 +149,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
if (expenseToEdit && data) {
reset({
projectId: data.project.id || "",
expensesCategoryId: data.expensesType.id || "",
expenseCategoryId: data.expensesCategory?.id || "",
paymentModeId: data.paymentMode.id || "",
paidById: data.paidBy.id || "",
transactionDate: data.transactionDate?.slice(0, 10) || "",
@ -245,7 +246,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
<select
className="form-select form-select-sm"
id="expensesCategoryId"
{...register("expensesCategoryId")}
{...register("expenseCategoryId")}
>
<option value="" disabled>
Select Type
@ -480,7 +481,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
</div>
</div>
<div className="row my-2 text-start">
<div className="row my-2 text-start">
<div className="col-md-12">
<Label className="form-label" required>
Upload Bill{" "}

File diff suppressed because it is too large Load Diff

View File

@ -7,56 +7,48 @@ const ALLOWED_TYPES = [
"image/jpg",
"image/jpeg",
];
export const PaymentRequestSchema = (expenseTypes,isItself) => {
return z
.object({
title: z.string().min(1, { message: "Project is required" }),
projectId: z.string().min(1, { message: "Project is required" }),
expenseCategoryId: z
.string()
.min(1, { message: "Expense Category is required" }),
currencyId: z
.string()
.min(1, { message: "Currency is required" }),
dueDate: z.string().min(1, { message: "Date is required" }),
description: z.string().min(1, { message: "Description is required" }),
payee: z.string().min(1, { message: "Supplier name is required" }),
isAdvancePayment: z.boolean().optional(),
amount: z.coerce
.number({
invalid_type_error: "Amount is required and must be a number",
export const PaymentRequestSchema = (expenseTypes, isItself) => {
return z.object({
title: z.string().min(1, { message: "Project is required" }),
projectId: z.string().min(1, { message: "Project is required" }),
expenseCategoryId: z
.string()
.min(1, { message: "Expense Category is required" }),
currencyId: z.string().min(1, { message: "Currency is required" }),
dueDate: z.string().min(1, { message: "Date is required" }),
description: z.string().min(1, { message: "Description is required" }),
payee: z.string().min(1, { message: "Supplier name is required" }),
isAdvancePayment: z.boolean().optional(),
amount: z.coerce
.number({
invalid_type_error: "Amount is required and must be a number",
})
.min(1, "Amount must be Enter")
.refine((val) => /^\d+(\.\d{1,2})?$/.test(val.toString()), {
message: "Amount must have at most 2 decimal places",
}),
billAttachments: z
.array(
z.object({
fileName: z.string().min(1, { message: "Filename is required" }),
base64Data: z.string().nullable(),
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),
})
.min(1, "Amount must be Enter")
.refine((val) => /^\d+(\.\d{1,2})?$/.test(val.toString()), {
message: "Amount must have at most 2 decimal places",
}),
billAttachments: z
.array(
z.object({
fileName: z.string().min(1, { message: "Filename is required" }),
base64Data: z.string().nullable(),
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),
})
).refine((data)=>{
if(isItself){
payee.z.string().optional();
}
}),
})
};
)
.optional(),
});
};
export const defaultPaymentRequest = {
title:"",
title: "",
description: "",
payee: "",
currencyId: "",
@ -64,11 +56,10 @@ export const defaultPaymentRequest = {
dueDate: "",
projectId: "",
expenseCategoryId: "",
isAdvancePayment:boolean,
isAdvancePayment: boolean,
billAttachments: [],
};
export const SearchPaymentRequestSchema = z.object({
projectIds: z.array(z.string()).optional(),
statusIds: z.array(z.string()).optional(),
@ -91,7 +82,6 @@ export const defaultPaymentRequestFilter = {
endDate: null,
};
export const PaymentRequestActionScheam = (
isTransaction = false,
transactionDate
@ -103,14 +93,13 @@ export const PaymentRequestActionScheam = (
paymentRequestId: z.string().nullable().optional(),
paidAt: z.string().nullable().optional(),
paidById: z.string().nullable().optional(),
})
.superRefine((data, ctx) => {
if (isTransaction) {
if (!data.paymentRequestId?.trim()) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["reimburseTransactionId"],
path: ["paymentRequestId"],
message: "Reimburse Transaction ID is required",
});
}

View File

@ -111,7 +111,7 @@ const ViewPaymentRequest = ({ requestId }) => {
};
return (
<form className="container px-3" onSubmit={handleSubmit(onSubmit)}>
<form className="container-xl px-3" onSubmit={handleSubmit(onSubmit)}>
<div className="col-12 mb-1">
<h5 className="fw-semibold m-0">Payment Request Details</h5>
<hr />
@ -325,14 +325,14 @@ const ViewPaymentRequest = ({ requestId }) => {
<label className="fw-semibold form-label">Description : </label>
<div className="text-muted">{data?.description}</div>
</div>
<div className="col-6 text-start">
<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?.length > 0 ? (
data?.documents?.map((doc) => {
{data?.attachments?.length > 0 ? (
data?.attachments?.map((doc) => {
const isImage = doc?.contentType?.includes("image");
return (

View File

@ -54,7 +54,7 @@ const Timeline = ({ items = [], transparent = true }) => {
<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}>
<li key={i} className="m-0" title={user.name}>
{user.avatar ? (
<img
src={user.avatar}
@ -64,7 +64,7 @@ const Timeline = ({ items = [], transparent = true }) => {
height="32"
/>
) : (
<Avatar
<Avatar size="xs"
firstName={user.firstName}
lastName={user.lastName}
/>
@ -75,7 +75,7 @@ const Timeline = ({ items = [], transparent = true }) => {
{item.users?.length === 1 && (
<div className="m-0">
<p className="mb-0 small fw-medium">{`${item.users[0].firstName} ${item.users[0].lastName}`}</p>
<p className="mb-0 text-xs fw-medium">{`${item.users[0].firstName} ${item.users[0].lastName}`}</p>
<small>{item.users[0].role}</small>
</div>
)}

View File

@ -5,6 +5,19 @@ import { queryClient } from "../layouts/AuthLayout";
import { useSelector } from "react-redux";
import moment from "moment";
export const usePayee =()=>{
return useQuery({
queryKey:["payee"],
queryFn:async()=>{
const resp = await ExpenseRepository.GetPayee();
return resp.data;
}
})
}
// -------------------Query------------------------------------------------------
const cleanFilter = (filter) => {

View File

@ -8,6 +8,7 @@ import PaymentRequestList from "../../components/PaymentRequest/PaymentRequestLi
import PaymentRequestFilterPanel from "../../components/PaymentRequest/PaymentRequestFilterPanel";
import { defaultPaymentRequestFilter,SearchPaymentRequestSchema } from "../../components/PaymentRequest/PaymentRequestSchema";
import ViewPaymentRequest from "../../components/PaymentRequest/ViewPaymentRequest";
import PreviewDocument from "../../components/Expenses/PreviewDocument";
export const PaymentRequestContext = createContext();
export const usePaymentRequestContext = () => {
@ -25,12 +26,16 @@ const PaymentRequestPage = () => {
const [ViewRequest,setVieRequest] = useState({view:false,requestId:null})
const { setOffcanvasContent, setShowTrigger } = useFab();
const [filters, setFilters] = useState(defaultPaymentRequestFilter);
const [ViewDocument, setDocumentView] = useState({
IsOpen: false,
Image: null,
});
const [search, setSearch] = useState("");
const contextValue = {
setManageRequest,
setVieRequest
setVieRequest,
setDocumentView
};
useEffect(() => {
@ -120,7 +125,7 @@ const PaymentRequestPage = () => {
{ViewRequest.view && (
<GlobalModel
isOpen
size="xl"
modalType="top"
closeModal={() => setVieRequest({ requestId: null, view: false })}
>
@ -128,6 +133,17 @@ const PaymentRequestPage = () => {
</GlobalModel>
)}
{ViewDocument.IsOpen && (
<GlobalModel
isOpen
size="md"
key={ViewDocument.Image ?? "doc"}
closeModal={() => setDocumentView({ IsOpen: false, Image: null })}
>
<PreviewDocument imageUrl={ViewDocument.Image} />
</GlobalModel>
)}
</div>
</PaymentRequestContext.Provider>
);

View File

@ -1,37 +1,50 @@
import { api } from "../utils/axiosClient";
const ExpenseRepository = {
GetPayee: () => api.get("/api/Expense/payment-request/payee"),
//#region Expense
GetExpenseList: (pageSize, pageNumber, filter, searchString) => {
const payloadJsonString = JSON.stringify(filter);
return api.get(`/api/expense/list?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}`);
return api.get(
`/api/expense/list?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}`
);
},
GetExpenseDetails: (id) => api.get(`/api/Expense/details/${id}`),
CreateExpense: (data) => api.post("/api/Expense/create", data),
UpdateExpense: (id, data) => api.put(`/api/Expense/edit/${id}`, data),
DeleteExpense: (id) => api.delete(`/api/Expense/delete/${id}`),
ActionOnExpense: (data) => api.post('/api/expense/action', data),
GetExpenseFilter: () => api.get('/api/Expense/filter'),
ActionOnExpense: (data) => api.post("/api/expense/action", data),
GetExpenseFilter: () => api.get("/api/Expense/filter"),
//#endregion
//#region Payment Request
GetPaymentRequestList: (pageSize, pageNumber, filter, isActive, searchString) => {
GetPaymentRequestList: (
pageSize,
pageNumber,
filter,
isActive,
searchString
) => {
const payloadJsonString = JSON.stringify(filter);
return api.get(`/api/Expense/get/payment-requests/list?isActive=${isActive}&pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}`);
return api.get(
`/api/Expense/get/payment-requests/list?isActive=${isActive}&pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}`
);
},
CreatePaymentRequest: (data) => api.post("/api/expense/payment-request/create", data),
UpdatePaymentRequest: (id, data) => api.put(`/api/Expense/payment-request/edit/${id}`, data),
GetPaymentRequest: (id) => api.get(`/api/Expense/get/payment-request/details/${id}`),
GetPaymentRequestFilter: () => api.get('/api/Expense/payment-request/filter'),
ActionOnPaymentRequest: (data) => api.post('/api/Expense/payment-request/action', data),
CreatePaymentRequest: (data) =>
api.post("/api/expense/payment-request/create", data),
UpdatePaymentRequest: (id, data) =>
api.put(`/api/Expense/payment-request/edit/${id}`, data),
GetPaymentRequest: (id) =>
api.get(`/api/Expense/get/payment-request/details/${id}`),
GetPaymentRequestFilter: () => api.get("/api/Expense/payment-request/filter"),
ActionOnPaymentRequest: (data) =>
api.post("/api/Expense/payment-request/action", data),
//#endregion
//#region Advance Payment
GetTranctionList: () => api.get(`/get/transactions/${employeeId}`)
GetTranctionList: () => api.get(`/get/transactions/${employeeId}`),
//#endregion
}
};
export default ExpenseRepository;