-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ >
);
};
-export default ExpenseFilterPanel;
+export default ExpenseFilterPanel;
\ No newline at end of file
diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx
index c44be107..ea9f41c6 100644
--- a/src/components/Expenses/ExpenseList.jsx
+++ b/src/components/Expenses/ExpenseList.jsx
@@ -5,13 +5,13 @@ import { useExpenseContext } from "../../pages/Expense/ExpensePage";
import { formatDate, formatUTCToLocalTime } from "../../utils/dateUtils";
import Pagination from "../common/Pagination";
import { APPROVE_EXPENSE } from "../../utils/constants";
-import { getColorNameFromHex } from "../../utils/appUtils";
+import { getColorNameFromHex, useDebounce } from "../../utils/appUtils";
import { ExpenseTableSkeleton } from "./ExpenseSkeleton";
import ConfirmModal from "../common/ConfirmModal";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { useSelector } from "react-redux";
-const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
+const ExpenseList = ({ filters, groupBy = "transactionDate",searchText }) => {
const [deletingId, setDeletingId] = useState(null);
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const { setViewExpense, setManageExpenseModal } = useExpenseContext();
@@ -19,12 +19,14 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE);
const [currentPage, setCurrentPage] = useState(1);
const pageSize = 20;
+ const debouncedSearch = useDebounce(searchText, 500);
const { mutate: DeleteExpense, isPending } = useDeleteExpense();
const { data, isLoading, isError, isInitialLoading, error } = useExpenseList(
pageSize,
currentPage,
- filters
+ filters,
+ debouncedSearch
);
const SelfId = useSelector(
@@ -74,6 +76,9 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
case "expensesType":
key = item.expensesType?.name || "Unknown Type";
break;
+ case "createdAt":
+ key = item.createdAt?.split("T")[0] || "Unknown Type";
+ break;
default:
key = "Others";
}
diff --git a/src/components/Expenses/ExpenseSchema.js b/src/components/Expenses/ExpenseSchema.js
index 901239ad..c74bcd38 100644
--- a/src/components/Expenses/ExpenseSchema.js
+++ b/src/components/Expenses/ExpenseSchema.js
@@ -8,69 +8,80 @@ const ALLOWED_TYPES = [
"image/jpeg",
];
-
export const ExpenseSchema = (expenseTypes) => {
return z
.object({
projectId: z.string().min(1, { message: "Project is required" }),
- expensesTypeId: z.string().min(1, { message: "Expense type is required" }),
+ expensesTypeId: z
+ .string()
+ .min(1, { message: "Expense type is required" }),
paymentModeId: z.string().min(1, { message: "Payment mode is required" }),
paidById: z.string().min(1, { message: "Employee name is required" }),
transactionDate: z
- .string()
- .min(1, { message: "Date is required" })
- .refine((val) => {
- const selected = new Date(val);
- const today = new Date();
+ .string()
+ .min(1, { message: "Date is required" })
+ .refine(
+ (val) => {
+ const selected = new Date(val);
+ const today = new Date();
- // Set both to midnight to avoid time-related issues
- selected.setHours(0, 0, 0, 0);
- today.setHours(0, 0, 0, 0);
+ // Set both to midnight to avoid time-related issues
+ selected.setHours(0, 0, 0, 0);
+ today.setHours(0, 0, 0, 0);
- return selected <= today;
- }, { message: "Future dates are not allowed" }),
+ return selected <= today;
+ },
+ { message: "Future dates are not allowed" }
+ ),
transactionId: z.string().optional(),
description: z.string().min(1, { message: "Description is required" }),
location: z.string().min(1, { message: "Location is required" }),
supplerName: z.string().min(1, { message: "Supplier name is required" }),
- amount: z
- .coerce
- .number({ invalid_type_error: "Amount is required and must be a number" })
+ 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",
}),
- noOfPersons: z.coerce
- .number()
- .optional(),
+ noOfPersons: z.coerce.number().optional(),
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(),
+ 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)
+ isActive: z.boolean().default(true),
})
)
.nonempty({ message: "At least one file attachment is required" }),
+ reimburseTransactionId: z.string().optional(),
+ reimburseDate: z.string().optional(),
+ reimburseById: z.string().optional(),
+
})
.refine(
(data) => {
- return !data.projectId || (data.paidById && data.paidById.trim() !== "");
+ return (
+ !data.projectId || (data.paidById && data.paidById.trim() !== "")
+ );
},
{
message: "Please select who paid (employee)",
path: ["paidById"],
}
)
- .superRefine((data, ctx) => {
+ .superRefine((data, ctx) => {
const expenseType = expenseTypes.find((et) => et.id === data.expensesTypeId);
if (expenseType?.noOfPersonsRequired && (!data.noOfPersons || data.noOfPersons < 1)) {
ctx.addIssue({
@@ -79,45 +90,70 @@ export const ExpenseSchema = (expenseTypes) => {
path: ["noOfPersons"],
});
}
+
+ if (isEndProcess) {
+ if (!data.reimburseTransactionId || data.reimburseTransactionId.trim() === "") {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Reimburse Transaction ID is required",
+ path: ["reimburseTransactionId"],
+ });
+ }
+ if (!data.reimburseDate) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Reimburse Date is required",
+ path: ["reimburseDate"],
+ });
+ }
+ if (!data.reimburseById) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Reimburse By is required",
+ path: ["reimburseById"],
+ });
+ }
+ }
});
};
export const defaultExpense = {
- projectId: "",
- expensesTypeId: "",
- paymentModeId: "",
- paidById: "",
- transactionDate: "",
- transactionId: "",
- description: "",
- location: "",
- supplerName: "",
- amount: "",
- noOfPersons: "",
- billAttachments: [],
- }
+ projectId: "",
+ expensesTypeId: "",
+ paymentModeId: "",
+ paidById: "",
+ transactionDate: "",
+ transactionId: "",
+ description: "",
+ location: "",
+ supplerName: "",
+ amount: "",
+ noOfPersons: "",
+ billAttachments: [],
+};
export const ActionSchema = z.object({
- comment : z.string().min(1,{message:"Please leave comment"}),
- selectedStatus: z.string().min(1, { message: "Please select a status" }),
-})
+ comment: z.string().min(1, { message: "Please leave comment" }),
+ selectedStatus: z.string().min(1, { message: "Please select a status" }),
+});
-
-export const SearchSchema = z.object({
- projectIds: z.array(z.string()).optional(),
+export const SearchSchema = z.object({
+ projectIds: z.array(z.string()).optional(),
statusIds: z.array(z.string()).optional(),
createdByIds: z.array(z.string()).optional(),
paidById: z.array(z.string()).optional(),
-startDate: z.string().optional(),
-endDate: z.string().optional(),
-
+ startDate: z.string().optional(),
+ endDate: z.string().optional(),
+ isTransactionDate: z.boolean().default(true),
});
export const defaultFilter = {
- projectIds:[],
- statusIds:[],
- createdByIds:[],
- paidById:[],
- startDate:null,
- endDate:null
-}
\ No newline at end of file
+ projectIds: [],
+ statusIds: [],
+ createdByIds: [],
+ paidById: [],
+ isTransactionDate: true,
+ startDate: null,
+ endDate: null,
+};
+
diff --git a/src/components/Expenses/ExpenseSkeleton.jsx b/src/components/Expenses/ExpenseSkeleton.jsx
index e4a3f208..8f73db5c 100644
--- a/src/components/Expenses/ExpenseSkeleton.jsx
+++ b/src/components/Expenses/ExpenseSkeleton.jsx
@@ -222,3 +222,62 @@ export const ExpenseTableSkeleton = ({ groups = 3, rowsPerGroup = 3 }) => {
);
};
+
+
+export const ExpenseFilterSkeleton = () => {
+ return (
+