added currency,rename of ExpenseType to expenseCategory - hooks,( expenseCategoryIds = fileter object)

This commit is contained in:
pramod.mahajan 2025-11-08 09:10:35 +05:30
parent 6a97dcf5f6
commit 6f228ed5f1
8 changed files with 167 additions and 162 deletions

View File

@ -4,21 +4,19 @@ const ExpenseFilterChips = ({ filters, filterData, removeFilterChip }) => {
// Build chips from filters
const filterChips = useMemo(() => {
const chips = [];
const buildGroup = (ids, list, label, key) => {
if (!ids?.length) return;
const items = ids.map((id) => ({
id,
name: list.find((item) => item.id === id)?.name || id,
name: list?.find((item) => item.id === id)?.name || id,
}));
chips.push({ key, label, items });
};
buildGroup(filters.projectIds, filterData.projects, "Project", "projectIds");
buildGroup(filters.createdByIds, filterData.createdBy, "Submitted By", "createdByIds");
buildGroup(filters.paidById, filterData.paidBy, "Paid By", "paidById");
buildGroup(filters.statusIds, filterData.status, "Status", "statusIds");
buildGroup(filters.ExpenseTypeIds, filterData.expensesType, "Category", "ExpenseTypeIds");
buildGroup(filters.expenseCategoryIds, filterData.expenseCategory, "Category", "expenseCategoryIds");
if (filters.startDate || filters.endDate) {
const start = filters.startDate

View File

@ -31,7 +31,7 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
{ id: "submittedBy", name: "Submitted By" },
{ id: "project", name: "Project" },
{ id: "paymentMode", name: "Payment Mode" },
{ id: "expensesType", name: "Expense Category" },
{ id: "expenseCategory", name: "Expense Category" },
{ id: "createdAt", name: "Submitted Date" },
].sort((a, b) => a.name.localeCompare(b.name));
}, []);
@ -46,7 +46,7 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
projectIds: defaultFilter.projectIds || [],
createdByIds: defaultFilter.createdByIds || [],
paidById: defaultFilter.paidById || [],
ExpenseCategoryIds: defaultFilter.ExpenseCategoryIds || [],
expenseCategoryIds: defaultFilter.expenseCategoryIds || [],
isTransactionDate: defaultFilter.isTransactionDate ?? true,
startDate: defaultFilter.startDate,
endDate: defaultFilter.endDate,
@ -214,7 +214,7 @@ const ExpenseFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilterdata }
valueKey="id"
/>
<SelectMultiple
name="ExpenseCategoryIds"
name="expenseCategoryIds"
label="Category :"
options={data.expenseCategory}
labelKey={(item) => item.name}

View File

@ -129,7 +129,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
align: "text-start mx-2",
},
{
key: "expensesCategory",
key: "expenseCategory",
label: "Expense Category",
getValue: (e) => e.expenseCategory?.name || "N/A",
align: "text-start",
@ -225,6 +225,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
]?.includes(groupBy);
const canEditExpense = (expense) => {
debugger;
return (
(expense?.status?.id === EXPENSE_DRAFT ||
EXPENSE_REJECTEDBY.includes(expense?.status?.id)) &&
@ -352,61 +353,58 @@ const ExpenseList = ({ filters, groupBy = "transactionDate", searchText }) => {
})
}
></i>
{canDetetExpense(expense) &&
canEditExpense(expense) && (
<div className="dropdown z-2">
<button
type="button"
className="btn btn-xs btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0 m-0"
data-bs-toggle="dropdown"
aria-expanded="false"
{canEditExpense(expense) && (
<div className="dropdown z-2">
<button
type="button"
className="btn btn-xs btn-icon btn-text-secondary rounded-pill dropdown-toggle hide-arrow p-0 m-0"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<i
className="bx bx-dots-vertical-rounded text-muted p-0"
data-bs-toggle="tooltip"
data-bs-offset="0,8"
data-bs-placement="top"
data-bs-custom-class="tooltip-dark"
title="More Action"
></i>
</button>
<ul className="dropdown-menu dropdown-menu-end w-auto">
<li
onClick={() =>
setManageExpenseModal({
IsOpen: true,
expenseId: expense.id,
})
}
>
<i
className="bx bx-dots-vertical-rounded text-muted p-0"
data-bs-toggle="tooltip"
data-bs-offset="0,8"
data-bs-placement="top"
data-bs-custom-class="tooltip-dark"
title="More Action"
></i>
</button>
<ul className="dropdown-menu dropdown-menu-end w-auto">
{canDetetExpense(expense) && (
<li
onClick={() =>
setManageExpenseModal({
IsOpen: true,
expenseId: expense.id,
})
}
>
<a className="dropdown-item px-2 cursor-pointer py-1">
<i className="bx bx-edit text-primary bx-xs me-2"></i>
<span className="align-left ">
Modify
</span>
</a>
</li>
)}
<a className="dropdown-item px-2 cursor-pointer py-1">
<i className="bx bx-edit text-primary bx-xs me-2"></i>
<span className="align-left ">
Modify
</span>
</a>
</li>
{canDetetExpense(expense) && (
<li
onClick={() => {
setIsDeleteModalOpen(true);
setDeletingId(expense.id);
}}
>
<a className="dropdown-item px-2 cursor-pointer py-1">
<i className="bx bx-trash text-danger bx-xs me-2"></i>
<span className="align-left">
Delete
</span>
</a>
</li>
)}
</ul>
</div>
)}
{canDetetExpense(expense) && (
<li
onClick={() => {
setIsDeleteModalOpen(true);
setDeletingId(expense.id);
}}
>
<a className="dropdown-item px-2 cursor-pointer py-1">
<i className="bx bx-trash text-danger bx-xs me-2"></i>
<span className="align-left">
Delete
</span>
</a>
</li>
)}
</ul>
</div>
)}
</div>
</td>
</tr>

View File

@ -10,7 +10,7 @@ const ALLOWED_TYPES = [
"image/jpeg",
];
export const ExpenseSchema = (ExpenseCategories) => {
export const ExpenseSchema = (expenseCategories) => {
return z
.object({
projectId: z.string().min(1, { message: "Project is required" }),
@ -70,12 +70,12 @@ export const ExpenseSchema = (ExpenseCategories) => {
}
)
.superRefine((data, ctx) => {
const ExpenseCategory = ExpenseCategories.find(
(et) => et.id === data.expenseCategoryId
const ExpenseCategory = expenseCategories?.find(
(et) => et.id === data?.expenseCategoryId
);
if (
ExpenseCategory?.noOfPersonsRequired &&
(!data.noOfPersons || data.noOfPersons < 1)
(!data?.noOfPersons || data?.noOfPersons < 1)
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
@ -168,7 +168,7 @@ export const defaultActionValues = {
reimburseDate: null,
reimburseById: null,
tdsPercentage: null,
baseAmount:null,
baseAmount: null,
taxAmount: null,
};
@ -177,7 +177,7 @@ export const SearchSchema = z.object({
statusIds: z.array(z.string()).optional(),
createdByIds: z.array(z.string()).optional(),
paidById: z.array(z.string()).optional(),
ExpenseCategoryIds: z.array(z.string()).optional(),
expenseCategoryIds: z.array(z.string()).optional(),
startDate: z.string().optional(),
endDate: z.string().optional(),
isTransactionDate: z.boolean().default(true),
@ -188,7 +188,7 @@ export const defaultFilter = {
statusIds: [],
createdByIds: [],
paidById: [],
ExpenseCategoryIds: [],
expenseCategoryIds: [],
isTransactionDate: true,
startDate: null,
endDate: null,

View File

@ -3,10 +3,11 @@ import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { defaultExpense, ExpenseSchema } from "./ExpenseSchema";
import { formatFileSize, localToUtc } from "../../utils/appUtils";
import { useProjectName } from "../../hooks/useProjects";
import { useProjectName } from "../../hooks/useProjects";
import { useDispatch, useSelector } from "react-redux";
import { changeMaster } from "../../slices/localVariablesSlice";
import useMaster, {
useCurrencies,
useExpenseCategory,
useExpenseStatus,
usePaymentMode,
@ -29,6 +30,8 @@ import DatePicker from "../common/DatePicker";
import ErrorPage from "../../pages/ErrorPage";
import Label from "../common/Label";
import EmployeeSearchInput from "../common/EmployeeSearchInput";
import Filelist from "./Filelist";
import { DEFAULT_CURRENCY } from "../../utils/constants";
const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
const {
@ -36,7 +39,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
isLoading,
error: ExpenseErrorLoad,
} = useExpense(expenseToEdit);
const [ExpenseType, setExpenseType] = useState();
const [expenseCategory, setExpenseCategory] = useState();
const dispatch = useDispatch();
const {
expenseCategories,
@ -65,7 +68,11 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
error,
isError: isProjectError,
} = useProjectName();
const {
data: currencies,
isLoading: currencyLoading,
error: currencyError,
} = useCurrencies();
const {
PaymentModes,
loading: PaymentModeLoading,
@ -81,6 +88,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
isLoading: EmpLoading,
isError: isEmployeeError,
} = useEmployeesNameByProject(selectedproject);
const files = watch("billAttachments");
const onFileChange = async (e) => {
const newFiles = Array.from(e.target.files);
@ -146,7 +154,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
if (expenseToEdit && data) {
reset({
projectId: data.project.id || "",
expenseCategoryId: data?.expenseCategory?.id || "",
expenseCategoryId: data?.expenseCategory?.id || "",
paymentModeId: data.paymentMode.id || "",
paidById: data.paidBy.id || "",
transactionDate: data.transactionDate?.slice(0, 10) || "",
@ -157,6 +165,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
amount: data.amount || "",
noOfPersons: data.noOfPersons || "",
gstNumber: data.gstNumber || "",
currencyId: data.currencyId || DEFAULT_CURRENCY,
billAttachments: data.documents
? data.documents.map((doc) => ({
fileName: doc.fileName,
@ -195,7 +204,9 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
const expenseCategoryId = watch("expenseCategoryId");
useEffect(() => {
setExpenseType(expenseCategories?.find((type) => type.id === expenseCategoryId));
setExpenseCategory(
expenseCategories?.find((type) => type.id === expenseCategoryId)
);
}, [expenseCategoryId]);
const handleClose = () => {
@ -238,7 +249,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
<div className="col-md-6">
<Label htmlFor="expenseCategoryId" className="form-label" required>
Expense Type
Expense Category
</Label>
<select
className="form-select form-select-sm"
@ -246,7 +257,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
{...register("expenseCategoryId")}
>
<option value="" disabled>
Select Type
Select Category
</option>
{ExpenseLoading ? (
<option disabled>Loading...</option>
@ -258,9 +269,9 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
))
)}
</select>
{errors.expenseCategoryId && (
{errors.expensesCategoryId && (
<small className="danger-text">
{errors.expenseCategoryId.message}
{errors.expensesCategoryId.message}
</small>
)}
</div>
@ -295,34 +306,10 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
</small>
)}
</div>
<div className="col-12 col-md-6 text-start">
<Label htmlFor="paidById" className="form-label" required>
Paid By
<Label className="form-label" required>
Paid By{" "}
</Label>
{/* <select
className="form-select form-select-sm"
id="paymentModeId"
{...register("paidById")}
disabled={!selectedproject}
>
<option value="" disabled>
Select Person
</option>
{EmpLoading ? (
<option disabled>Loading...</option>
) : (
employees?.map((emp) => (
<option key={emp.id} value={emp.id}>
{`${emp.firstName} ${emp.lastName} `}
</option>
))
)}
</select>
{errors.paidById && (
<small className="danger-text">{errors.paidById.message}</small>
)} */}
<EmployeeSearchInput
control={control}
name="paidById"
@ -339,6 +326,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
</Label>
<DatePicker
name="transactionDate"
className="w-100"
control={control}
maxDate={new Date()}
/>
@ -436,24 +424,52 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
)}
</div>
{ExpenseType?.noOfPersonsRequired && (
<div className="col-md-6 mt-2 text-start">
<label className="form-label ">No. of Persons</label>
<input
type="number"
id="noOfPersons"
className="form-control form-control-sm"
{...register("noOfPersons")}
inputMode="numeric"
/>
{errors.noOfPersons && (
<small className="danger-text">
{errors.noOfPersons.message}
</small>
)}
</div>
)}
</div>
<div className="row">
<div className="col-md-6 text-start ">
<Label htmlFor="currencyId" className="form-label" required>
Select Currency
</Label>
<select
className="form-select form-select-sm"
id="currencyId"
{...register("currencyId")}
>
<option value="" disabled>
Select Currency
</option>
{currencyLoading ? (
<option disabled>Loading...</option>
) : (
currencies?.map((currency) => (
<option key={currency.id} value={currency.id}>
{`${currency.currencyName} (${currency.symbol}) `}
</option>
))
)}
</select>
{errors.currencyId && (
<small className="danger-text">{errors.currencyId.message}</small>
)}
</div>
{expenseCategory?.noOfPersonsRequired && (
<div className="col-md-6 text-start">
<Label className="form-label" required>No. of Persons</Label>
<input
type="number"
id="noOfPersons"
className="form-control form-control-sm"
{...register("noOfPersons")}
inputMode="numeric"
/>
{errors.noOfPersons && (
<small className="danger-text">
{errors.noOfPersons.message}
</small>
)}
</div>
)}
</div>
<div className="row my-2 text-start">
<div className="col-md-12">
@ -510,40 +526,11 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
</small>
)}
{files.length > 0 && (
<div className="d-block">
{files
.filter((file) => {
if (expenseToEdit) {
return file.isActive;
}
return true;
})
.map((file, idx) => (
<a
key={idx}
className="d-flex justify-content-between text-start p-1"
href={file.preSignedUrl || "#"}
target="_blank"
rel="noopener noreferrer"
>
<div>
<span className="mb-0 text-secondary small d-block">
{file.fileName}
</span>
<span className="text-body-secondary small d-block">
{file.fileSize ? formatFileSize(file.fileSize) : ""}
</span>
</div>
<i
className="bx bx-trash bx-sm cursor-pointer text-danger"
onClick={(e) => {
e.preventDefault();
removeFile(expenseToEdit ? file.documentId : idx);
}}
></i>
</a>
))}
</div>
<Filelist
files={files}
removeFile={removeFile}
expenseToEdit={expenseToEdit}
/>
)}
{Array.isArray(errors.billAttachments) &&

View File

@ -10,6 +10,16 @@ import {
} from "@tanstack/react-query";
import showToast from "../../services/toastService";
export const useCurrencies = () => {
return useQuery({
queryKey: ["currencies"],
queryFn: async () => {
const resp = await MasterRespository.getCurrencies();
return resp.data;
},
});
};
export const usePaymentAjustmentHead = (isActive) => {
return useQuery({
queryKey: ["paymentType",isActive],
@ -171,7 +181,7 @@ export const useExpenseCategory = () => {
},
});
return { ExpenseTypes, loading, error };
return { expenseCategories, loading, error };
};
export const usePaymentMode = () => {
const {

View File

@ -190,7 +190,7 @@ const ExpensePage = () => {
{viewExpense.view && (
<GlobalModel
isOpen
size="lg"
size="xl"
modalType="top"
closeModal={() => setViewExpense({ expenseId: null, view: false })}
>

View File

@ -50,7 +50,10 @@ export const MasterRespository = {
"Contact Category": (id) => api.delete(`/api/master/contact-category/${id}`),
"Contact Tag": (id) => api.delete(`/api/master/contact-tag/${id}`),
"Expense Type": (id, isActive) =>
api.delete(`/api/Master/expenses-category/delete/${id}`, (isActive = false)),
api.delete(
`/api/Master/expenses-category/delete/${id}`,
(isActive = false)
),
"Payment Mode": (id, isActive) =>
api.delete(`/api/Master/payment-mode/delete/${id}`, (isActive = false)),
"Expense Status": (id, isActive) =>
@ -58,7 +61,11 @@ export const MasterRespository = {
"Document Type": (id) => api.delete(`/api/Master/document-type/delete/${id}`),
"Document Category": (id) =>
api.delete(`/api/Master/document-category/delete/${id}`),
"Payment Adjustment Head":(id,isActive)=>api.delete(`/api/Master/payment-adjustment-head/delete/${id}`,(isActive=false)),
"Payment Adjustment Head": (id, isActive) =>
api.delete(
`/api/Master/payment-adjustment-head/delete/${id}`,
(isActive = false)
),
getWorkCategory: () => api.get(`/api/master/work-categories`),
createWorkCategory: (data) => api.post(`/api/master/work-category`, data),
@ -79,7 +86,8 @@ export const MasterRespository = {
getAuditStatus: () => api.get("/api/Master/work-status"),
getExpenseCategories: () => api.get("/api/Master/expenses-categories"),
createExpenseCategory: (data) => api.post("/api/Master/expenses-category", data),
createExpenseCategory: (data) =>
api.post("/api/Master/expenses-category", data),
updateExpenseCategory: (id, data) =>
api.put(`/api/Master/expenses-category/edit/${id}`, data),
@ -134,6 +142,10 @@ export const MasterRespository = {
getPaymentAdjustmentHead: (isActive) =>
api.get(`/api/Master/payment-adjustment-head/list?isActive=${isActive}`),
createPaymentAjustmentHead:(data)=>api.post(`/api/Master/payment-adjustment-head`, data),
updatePaymentAjustmentHead:(id,data)=>api.put(`/api/Master/payment-adjustment-head/edit/${id}`, data)
createPaymentAjustmentHead: (data) =>
api.post(`/api/Master/payment-adjustment-head`, data),
updatePaymentAjustmentHead: (id, data) =>
api.put(`/api/Master/payment-adjustment-head/edit/${id}`, data),
getCurrencies: () => api.get(`/api/Master/currencies/list`),
};