added expenses Reimburse
This commit is contained in:
parent
b30940c2aa
commit
35f221038d
@ -73,21 +73,6 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mb-2 text-start px-2">
|
|
||||||
<label htmlFor="groupBySelect" className="form-label">Group By :</label>
|
|
||||||
<select
|
|
||||||
id="groupBySelect"
|
|
||||||
className="form-select form-select-sm"
|
|
||||||
value={selectedGroup?.id || ""}
|
|
||||||
onChange={handleGroupChange}
|
|
||||||
>
|
|
||||||
{groupByList.map((group) => (
|
|
||||||
<option key={group.id} value={group.id}>
|
|
||||||
{group.name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className="p-2 text-start">
|
<form onSubmit={handleSubmit(onSubmit)} className="p-2 text-start">
|
||||||
@ -174,6 +159,21 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mb-2 text-start ">
|
||||||
|
<label htmlFor="groupBySelect" className="form-label">Group By :</label>
|
||||||
|
<select
|
||||||
|
id="groupBySelect"
|
||||||
|
className="form-select form-select-sm"
|
||||||
|
value={selectedGroup?.id || ""}
|
||||||
|
onChange={handleGroupChange}
|
||||||
|
>
|
||||||
|
{groupByList.map((group) => (
|
||||||
|
<option key={group.id} value={group.id}>
|
||||||
|
{group.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="d-flex justify-content-end py-3 gap-2">
|
<div className="d-flex justify-content-end py-3 gap-2">
|
||||||
<button
|
<button
|
||||||
@ -189,6 +189,7 @@ const ExpenseFilterPanel = ({ onApply, handleGroupBy }) => {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -25,7 +25,6 @@ export const ExpenseSchema = (expenseTypes) => {
|
|||||||
const selected = new Date(val);
|
const selected = new Date(val);
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
|
|
||||||
// Set both to midnight to avoid time-related issues
|
|
||||||
selected.setHours(0, 0, 0, 0);
|
selected.setHours(0, 0, 0, 0);
|
||||||
today.setHours(0, 0, 0, 0);
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
@ -65,9 +64,7 @@ export const ExpenseSchema = (expenseTypes) => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.nonempty({ message: "At least one file attachment is required" }),
|
.nonempty({ message: "At least one file attachment is required" }),
|
||||||
reimburseTransactionId: z.string().optional(),
|
|
||||||
reimburseDate: z.string().optional(),
|
|
||||||
reimburseById: z.string().optional(),
|
|
||||||
|
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
@ -132,10 +129,53 @@ export const defaultExpense = {
|
|||||||
billAttachments: [],
|
billAttachments: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ActionSchema = z.object({
|
|
||||||
|
export const ExpenseActionScheam = (isReimbursement = false) => {
|
||||||
|
return z
|
||||||
|
.object({
|
||||||
comment: z.string().min(1, { message: "Please leave comment" }),
|
comment: z.string().min(1, { message: "Please leave comment" }),
|
||||||
selectedStatus: z.string().min(1, { message: "Please select a status" }),
|
statusId: z.string().min(1, { message: "Please select a status" }),
|
||||||
|
reimburseTransactionId: z.string().nullable().optional(),
|
||||||
|
reimburseDate: z.string().nullable().optional(),
|
||||||
|
reimburseById: z.string().nullable().optional(),
|
||||||
|
})
|
||||||
|
.superRefine((data, ctx) => {
|
||||||
|
if (isReimbursement) {
|
||||||
|
if (!data.reimburseTransactionId?.trim()) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["reimburseTransactionId"],
|
||||||
|
message: "Reimburse Transaction ID is required",
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
if (!data.reimburseDate) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["reimburseDate"],
|
||||||
|
message: "Reimburse Date is required",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!data.reimburseById) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["reimburseById"],
|
||||||
|
message: "Reimburse By is required",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultActionValues = {
|
||||||
|
comment: "",
|
||||||
|
statusId: "",
|
||||||
|
|
||||||
|
reimburseTransactionId: null,
|
||||||
|
reimburseDate: null,
|
||||||
|
reimburseById: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const SearchSchema = z.object({
|
export const SearchSchema = z.object({
|
||||||
projectIds: z.array(z.string()).optional(),
|
projectIds: z.array(z.string()).optional(),
|
||||||
|
|||||||
@ -206,7 +206,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
<form id="expenseForm" onSubmit={handleSubmit(onSubmit)}>
|
<form id="expenseForm" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="row my-2">
|
<div className="row my-2">
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label htmlFor="projectId" className="form-label">
|
<label className="form-label">
|
||||||
Select Project
|
Select Project
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
@ -230,7 +230,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label for="expensesTypeId" className="form-label ">
|
<label htmlFor="expensesTypeId" className="form-label ">
|
||||||
Expense Type
|
Expense Type
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
@ -261,7 +261,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
|
|
||||||
<div className="row my-2">
|
<div className="row my-2">
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label for="paymentModeId" className="form-label ">
|
<label htmlFor="paymentModeId" className="form-label ">
|
||||||
Payment Mode
|
Payment Mode
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
@ -290,7 +290,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label for="paidById" className="form-label ">
|
<label htmlFor="paidById" className="form-label ">
|
||||||
Paid By
|
Paid By
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
@ -320,7 +320,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
|
|
||||||
<div className="row my-2">
|
<div className="row my-2">
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label for="transactionDate" className="form-label ">
|
<label htmlFor="transactionDate" className="form-label ">
|
||||||
Transaction Date
|
Transaction Date
|
||||||
</label>
|
</label>
|
||||||
{/* <input
|
{/* <input
|
||||||
@ -343,7 +343,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label for="amount" className="form-label ">
|
<label htmlFor="amount" className="form-label ">
|
||||||
Amount
|
Amount
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@ -361,9 +361,9 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row my-2">
|
<div className="row my-2">
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label for="supplerName" className="form-label ">
|
<label htmlFor="supplerName" className="form-label ">
|
||||||
Supplier Name
|
Supplier Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@ -380,7 +380,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label for="location" className="form-label ">
|
<label htmlFor="location" className="form-label ">
|
||||||
Location
|
Location
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@ -396,7 +396,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="row my-2">
|
<div className="row my-2">
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label for="statusId" className="form-label ">
|
<label htmlFor="statusId" className="form-label ">
|
||||||
TransactionId
|
TransactionId
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@ -415,7 +415,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
|
|
||||||
{ExpenseType?.noOfPersonsRequired && (
|
{ExpenseType?.noOfPersonsRequired && (
|
||||||
<div className="col-md-6">
|
<div className="col-md-6">
|
||||||
<label htmlFor="noOfPersons" >
|
<label >
|
||||||
No. of Persons
|
No. of Persons
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@ -436,7 +436,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
|
|
||||||
<div className="row my-2">
|
<div className="row my-2">
|
||||||
<div className="col-md-12" >
|
<div className="col-md-12" >
|
||||||
<label for="description">Description</label>
|
<label htmlFor="description">Description</label>
|
||||||
<textarea
|
<textarea
|
||||||
id="description"
|
id="description"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
|
|||||||
@ -7,35 +7,43 @@ import {
|
|||||||
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { ActionSchema } from "./ExpenseSchema";
|
import { defaultActionValues, ExpenseActionScheam } from "./ExpenseSchema";
|
||||||
import { useExpenseContext } from "../../pages/Expense/ExpensePage";
|
import { useExpenseContext } from "../../pages/Expense/ExpensePage";
|
||||||
import { getColorNameFromHex } from "../../utils/appUtils";
|
import { getColorNameFromHex } from "../../utils/appUtils";
|
||||||
import { ExpenseDetailsSkeleton } from "./ExpenseSkeleton";
|
import { ExpenseDetailsSkeleton } from "./ExpenseSkeleton";
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import { REVIEW_EXPENSE } from "../../utils/constants";
|
import { PROCESS_EXPENSE, REVIEW_EXPENSE } from "../../utils/constants";
|
||||||
import { useProfile } from "../../hooks/useProfile";
|
import { useProfile } from "../../hooks/useProfile";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import Avatar from "../common/Avatar";
|
import Avatar from "../common/Avatar";
|
||||||
|
import Error from "../common/Error";
|
||||||
|
import DatePicker from "../common/DatePicker";
|
||||||
|
import { useEmployeeRoles, useEmployeesName } from "../../hooks/useEmployees";
|
||||||
|
import EmployeeSearchInput from "../common/EmployeeSearchInput";
|
||||||
|
import { z } from "zod";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
const ViewExpense = ({ ExpenseId }) => {
|
const ViewExpense = ({ ExpenseId }) => {
|
||||||
const { data, isLoading, isError, error } = useExpense(ExpenseId);
|
const { data, isLoading, isError, error } = useExpense(ExpenseId);
|
||||||
|
const [IsPaymentProcess, setIsPaymentProcess] = useState(false);
|
||||||
|
const [clickedStatusId, setClickedStatusId] = useState(null);
|
||||||
|
|
||||||
const IsReview = useHasUserPermission(REVIEW_EXPENSE);
|
const IsReview = useHasUserPermission(REVIEW_EXPENSE);
|
||||||
const [imageLoaded, setImageLoaded] = useState({});
|
const [imageLoaded, setImageLoaded] = useState({});
|
||||||
const { setDocumentView } = useExpenseContext();
|
const { setDocumentView } = useExpenseContext();
|
||||||
|
const ActionSchema = ExpenseActionScheam(IsPaymentProcess) ?? z.object({});
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
setValue,
|
setValue,
|
||||||
reset,
|
reset,
|
||||||
|
control,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm({
|
} = useForm({
|
||||||
resolver: zodResolver(ActionSchema),
|
resolver: zodResolver(ActionSchema),
|
||||||
defaultValues: {
|
defaultValues: defaultActionValues,
|
||||||
comment: "",
|
|
||||||
selectedStatus: "",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const userPermissions = useSelector(
|
const userPermissions = useSelector(
|
||||||
@ -51,24 +59,29 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
: [];
|
: [];
|
||||||
|
|
||||||
if (permissionIds.length === 0) return true;
|
if (permissionIds.length === 0) return true;
|
||||||
|
if (permissionIds.includes(PROCESS_EXPENSE)) {
|
||||||
|
setIsPaymentProcess(true);
|
||||||
|
}
|
||||||
return permissionIds.some((id) => userPermissions.includes(id));
|
return permissionIds.some((id) => userPermissions.includes(id));
|
||||||
});
|
});
|
||||||
}, [data, userPermissions]);
|
}, [data, userPermissions]);
|
||||||
|
|
||||||
const { mutate: MakeAction } = useActionOnExpense(() => reset());
|
const { mutate: MakeAction,isPending } = useActionOnExpense(() => {
|
||||||
|
setClickedStatusId(null);
|
||||||
|
reset()});
|
||||||
|
|
||||||
const onSubmit = (formData) => {
|
const onSubmit = (formData) => {
|
||||||
const Payload = {
|
const Payload = {
|
||||||
|
...formData,
|
||||||
|
reimburseDate:moment.utc(formData.reimburseDate, "DD-MM-YYYY").toISOString(),
|
||||||
expenseId: ExpenseId,
|
expenseId: ExpenseId,
|
||||||
statusId: formData.selectedStatus,
|
|
||||||
comment: formData.comment,
|
comment: formData.comment,
|
||||||
};
|
};
|
||||||
|
|
||||||
MakeAction(Payload);
|
MakeAction(Payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) return <ExpenseDetailsSkeleton />;
|
if (isLoading) return <ExpenseDetailsSkeleton />;
|
||||||
|
if (isError) return <Error error={error} />;
|
||||||
const handleImageLoad = (id) => {
|
const handleImageLoad = (id) => {
|
||||||
setImageLoaded((prev) => ({ ...prev, [id]: true }));
|
setImageLoaded((prev) => ({ ...prev, [id]: true }));
|
||||||
};
|
};
|
||||||
@ -81,67 +94,89 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Each info block below now has h-100 to stretch */}
|
{/* Row 1 */}
|
||||||
<div className="col-12 col-md-6 mb-3 h-100">
|
<div className="col-md-6 mb-3">
|
||||||
<div className="d-flex">
|
<div className="d-flex">
|
||||||
<label className="form-label me-2 mb-0 fw-semibold">
|
<label
|
||||||
|
className="form-label me-2 mb-0 fw-semibold text-start"
|
||||||
|
style={{ minWidth: "130px" }}
|
||||||
|
>
|
||||||
Transaction Date :
|
Transaction Date :
|
||||||
</label>
|
</label>
|
||||||
<div className="text-muted">
|
<div className="text-muted">
|
||||||
{formatUTCToLocalTime(data.transactionDate)}
|
{formatUTCToLocalTime(data?.transactionDate)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-md-6 mb-3">
|
||||||
<div className="col-12 col-md-6 mb-3 h-100">
|
|
||||||
<div className="d-flex">
|
<div className="d-flex">
|
||||||
<label className="form-label me-2 mb-0 fw-semibold">
|
<label
|
||||||
|
className="form-label me-2 mb-0 fw-semibold text-start"
|
||||||
|
style={{ minWidth: "130px" }}
|
||||||
|
>
|
||||||
Expense Type :
|
Expense Type :
|
||||||
</label>
|
</label>
|
||||||
<div className="text-muted">{data.expensesType.name}</div>
|
<div className="text-muted">{data?.expensesType?.name}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 col-md-4 mb-3 h-100">
|
{/* Row 2 */}
|
||||||
|
<div className="col-md-6 mb-3">
|
||||||
<div className="d-flex">
|
<div className="d-flex">
|
||||||
<label className="form-label me-2 mb-0 fw-semibold">
|
<label
|
||||||
|
className="form-label me-2 mb-0 fw-semibold text-start"
|
||||||
|
style={{ minWidth: "130px" }}
|
||||||
|
>
|
||||||
Supplier :
|
Supplier :
|
||||||
</label>
|
</label>
|
||||||
<div className="text-muted">{data.supplerName}</div>
|
<div className="text-muted">{data?.supplerName}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-md-6 mb-3">
|
||||||
<div className="col-12 col-md-4 mb-3 h-100">
|
|
||||||
<div className="d-flex">
|
<div className="d-flex">
|
||||||
<label className="form-label me-2 mb-0 fw-semibold">Amount :</label>
|
<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 className="text-muted">₹ {data.amount}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 col-md-4 mb-3 h-100">
|
{/* Row 3 */}
|
||||||
|
<div className="col-md-6 mb-3">
|
||||||
<div className="d-flex">
|
<div className="d-flex">
|
||||||
<label className="form-label me-2 mb-0 fw-semibold">
|
<label
|
||||||
|
className="form-label me-2 mb-0 fw-semibold text-start"
|
||||||
|
style={{ minWidth: "130px" }}
|
||||||
|
>
|
||||||
Payment Mode :
|
Payment Mode :
|
||||||
</label>
|
</label>
|
||||||
<div className="text-muted">{data.paymentMode.name}</div>
|
<div className="text-muted">{data?.paymentMode?.name}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-md-6 mb-3">
|
||||||
<div className="row align-items-center mb-3">
|
<div className="d-flex">
|
||||||
<div className="col-6">
|
<label
|
||||||
<div className="d-flex align-items-center">
|
className="form-label me-2 mb-0 fw-semibold text-start"
|
||||||
<label className="form-label me-2 mb-0 fw-semibold">
|
style={{ minWidth: "130px" }}
|
||||||
|
>
|
||||||
Paid By :
|
Paid By :
|
||||||
</label>
|
</label>
|
||||||
<div className="text-muted">
|
<div className="text-muted">
|
||||||
{data.paidBy.firstName} {data.paidBy.lastName}
|
{data?.paidBy?.firstName} {data?.paidBy?.lastName}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-6">
|
{/* Row 4 */}
|
||||||
<div className="d-flex align-items-center">
|
<div className="col-md-6 mb-3">
|
||||||
<label className="form-label me-2 mb-0 fw-semibold">
|
<div className="d-flex">
|
||||||
|
<label
|
||||||
|
className="form-label me-2 mb-0 fw-semibold text-start"
|
||||||
|
style={{ minWidth: "130px" }}
|
||||||
|
>
|
||||||
Status :
|
Status :
|
||||||
</label>
|
</label>
|
||||||
<span
|
<span
|
||||||
@ -153,30 +188,35 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="col-md-6 mb-3">
|
||||||
|
|
||||||
<div className="col-12 col-md-4 mb-3 h-100">
|
|
||||||
<div className="d-flex">
|
<div className="d-flex">
|
||||||
<label className="form-label me-2 mb-0 fw-semibold">
|
<label
|
||||||
|
className="form-label me-2 mb-0 fw-semibold text-start"
|
||||||
|
style={{ minWidth: "130px" }}
|
||||||
|
>
|
||||||
Pre-Approved :
|
Pre-Approved :
|
||||||
</label>
|
</label>
|
||||||
<div className="text-muted">{data.preApproved ? "Yes" : "No"}</div>
|
<div className="text-muted">{data.preApproved ? "Yes" : "No"}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 col-md-6 mb-3 h-100">
|
<div className="col-md-6 mb-3">
|
||||||
<div className="d-flex">
|
<div className="d-flex">
|
||||||
<label className="form-label me-2 mb-0 fw-semibold">
|
<label
|
||||||
|
className="form-label me-2 mb-0 fw-semibold text-start"
|
||||||
|
style={{ minWidth: "130px" }}
|
||||||
|
>
|
||||||
Project :
|
Project :
|
||||||
</label>
|
</label>
|
||||||
<div className="text-muted text-start">{data?.project?.name}</div>
|
<div className="text-muted">{data?.project?.name}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-md-6 mb-3">
|
||||||
<div className="row align-items-center mb-3">
|
<div className="d-flex">
|
||||||
<div className="col-12 col-md-auto">
|
<label
|
||||||
<div className="d-flex align-items-center">
|
className="form-label me-2 mb-0 fw-semibold text-start"
|
||||||
<label className="form-label me-2 mb-0 fw-semibold">
|
style={{ minWidth: "130px" }}
|
||||||
|
>
|
||||||
Created At :
|
Created At :
|
||||||
</label>
|
</label>
|
||||||
<div className="text-muted">
|
<div className="text-muted">
|
||||||
@ -185,10 +225,14 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Row 6 */}
|
||||||
{data.createdBy && (
|
{data.createdBy && (
|
||||||
<div className="col-12 col-md">
|
<div className="col-md-6 mb-3 text-start">
|
||||||
<div className="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" }}
|
||||||
|
>
|
||||||
Created By :
|
Created By :
|
||||||
</label>
|
</label>
|
||||||
<div className="d-flex align-items-center gap-1">
|
<div className="d-flex align-items-center gap-1">
|
||||||
@ -207,15 +251,16 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{data.reviewedBy && (
|
{data.reviewedBy && (
|
||||||
<div className="col-12 col-md-auto mb-3 h-100">
|
<div className="col-md-6 mb-3 text-start">
|
||||||
<div className="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" }}
|
||||||
|
>
|
||||||
Reviewed By :
|
Reviewed By :
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div className="d-flex align-items-center">
|
<div className="d-flex align-items-center">
|
||||||
<Avatar
|
<Avatar
|
||||||
size="xs"
|
size="xs"
|
||||||
@ -232,13 +277,16 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{data.approvedBy && (
|
|
||||||
<div className="col-12 col-md-auto mb-3 h-100">
|
|
||||||
<div className="d-flex align-items-center">
|
|
||||||
<label className="form-label me-2 mb-0 fw-semibold">
|
|
||||||
Approved By :
|
|
||||||
</label>
|
|
||||||
|
|
||||||
|
{data.approvedBy && (
|
||||||
|
<div className="col-md-6 mb-3 text-start">
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<label
|
||||||
|
className="form-label me-2 mb-0 fw-semibold"
|
||||||
|
style={{ minWidth: "130px" }}
|
||||||
|
>
|
||||||
|
Approved By :{" "}
|
||||||
|
</label>
|
||||||
<div className="d-flex align-items-center">
|
<div className="d-flex align-items-center">
|
||||||
<Avatar
|
<Avatar
|
||||||
size="xs"
|
size="xs"
|
||||||
@ -255,7 +303,65 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{data.processedBy && (
|
||||||
|
<div className="col-md-6 mb-3 text-start">
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<label
|
||||||
|
className="form-label me-2 mb-0 fw-semibold"
|
||||||
|
style={{ minWidth: "130px" }}
|
||||||
|
>
|
||||||
|
Processed By :{" "}
|
||||||
|
</label>
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<Avatar
|
||||||
|
size="xs"
|
||||||
|
classAvatar="m-0"
|
||||||
|
firstName={data.processedBy?.firstName}
|
||||||
|
lastName={data.processedBy?.lastName}
|
||||||
|
/>
|
||||||
|
<span className="text-muted">
|
||||||
|
{`${data.processedBy?.firstName ?? ""} ${
|
||||||
|
data.processedBy?.lastName ?? ""
|
||||||
|
}`.trim() || "N/A"}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{data.expensesReimburse && (<div className="row text-start">
|
||||||
|
<div className="col-md-6 mb-3">
|
||||||
|
<strong>Transaction ID :</strong> {data.expensesReimburse.reimburseTransactionId || "N/A"}
|
||||||
|
</div>
|
||||||
|
<div className="col-md-6 mb-3">
|
||||||
|
<strong>Reimburse Date :</strong>{" "}
|
||||||
|
{ moment(data.expensesReimburse.reimburseDate).format("DD-MM-YYYY") }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{data.expensesReimburse && (
|
||||||
|
<>
|
||||||
|
<div className="col-md-6 mb-3 d-flex align-items-center">
|
||||||
|
<strong className="me-2">Reimburse By :</strong>
|
||||||
|
<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 className="col-md-6 mb-3">
|
||||||
|
<strong>Note :</strong> {data.expensesReimburse.reimburseNote}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div className="text-start">
|
<div className="text-start">
|
||||||
<label className="form-label me-2 mb-0 fw-semibold">Description :</label>
|
<label className="form-label me-2 mb-0 fw-semibold">Description :</label>
|
||||||
@ -284,7 +390,7 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="list-group-item list-group-item-action d-flex align-items-center"
|
className="list-group-item list-group-item-action d-flex align-items-center"
|
||||||
key={doc.id}
|
key={doc.documentId}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="rounded me-1 d-flex align-items-center justify-content-center cursor-pointer"
|
className="rounded me-1 d-flex align-items-center justify-content-center cursor-pointer"
|
||||||
@ -325,25 +431,58 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
<hr className="divider my-1" />
|
<hr className="divider my-1" />
|
||||||
|
|
||||||
{Array.isArray(data?.nextStatus) && data.nextStatus.length > 0 && (
|
{Array.isArray(data?.nextStatus) && data.nextStatus.length > 0 && (
|
||||||
|
<>
|
||||||
|
{IsPaymentProcess && (
|
||||||
|
<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} />
|
||||||
|
{errors.reimburseDate && (
|
||||||
|
<small className="danger-text">
|
||||||
|
{errors.reimburseDate.message}
|
||||||
|
</small>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="col-12 col-md-6 text-start">
|
||||||
|
<label className="form-label">Employeee </label>
|
||||||
|
<EmployeeSearchInput
|
||||||
|
control={control}
|
||||||
|
name="reimburseById"
|
||||||
|
projectId={data?.project?.id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="col-12 mb-3 text-start">
|
<div className="col-12 mb-3 text-start">
|
||||||
{nextStatusWithPermission.length > 0 && (
|
{nextStatusWithPermission.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<label className="form-label me-2 mb-0 fw-semibold">
|
<label className="form-label me-2 mb-0 ">Comment:</label>
|
||||||
Comment:
|
|
||||||
</label>
|
|
||||||
<textarea
|
<textarea
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
{...register("comment")}
|
{...register("comment")}
|
||||||
rows="2"
|
rows="2"
|
||||||
/>
|
/>
|
||||||
{errors.comment && (
|
{errors.comment && (
|
||||||
<small className="danger-text">{errors.comment.message}</small>
|
<small className="danger-text">
|
||||||
|
{errors.comment.message}
|
||||||
|
</small>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<input type="hidden" {...register("selectedStatus")} />
|
|
||||||
|
|
||||||
{nextStatusWithPermission?.length > 0 && (
|
{nextStatusWithPermission?.length > 0 && (
|
||||||
<div className="text-center flex-wrap gap-2 my-2">
|
<div className="text-center flex-wrap gap-2 my-2">
|
||||||
{nextStatusWithPermission?.map((status, index) => (
|
{nextStatusWithPermission?.map((status, index) => (
|
||||||
@ -351,17 +490,20 @@ const ViewExpense = ({ ExpenseId }) => {
|
|||||||
key={status.id || index}
|
key={status.id || index}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setValue("selectedStatus", status.id);
|
setClickedStatusId(status.id);
|
||||||
|
setValue("statusId", status.id);
|
||||||
handleSubmit(onSubmit)();
|
handleSubmit(onSubmit)();
|
||||||
}}
|
}}
|
||||||
|
disabled={isPending}
|
||||||
className="btn btn-primary btn-sm cursor-pointer mx-2 border-0"
|
className="btn btn-primary btn-sm cursor-pointer mx-2 border-0"
|
||||||
>
|
>
|
||||||
{status.displayName || status.name}
|
{(isPending && clickedStatusId === status.id) ? "Please Wait..." : (status.displayName || status.name)}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
85
src/components/common/EmployeeSearchInput.jsx
Normal file
85
src/components/common/EmployeeSearchInput.jsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { useEmployeesName } from "../../hooks/useEmployees";
|
||||||
|
import { useDebounce } from "../../utils/appUtils";
|
||||||
|
import { useController } from "react-hook-form";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const EmployeeSearchInput = ({ control, name, projectId }) => {
|
||||||
|
const {
|
||||||
|
field: { onChange, value, ref },
|
||||||
|
fieldState: { error },
|
||||||
|
} = useController({ name, control });
|
||||||
|
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const [showDropdown, setShowDropdown] = useState(false);
|
||||||
|
const debouncedSearch = useDebounce(search, 500);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: employees,
|
||||||
|
isLoading,
|
||||||
|
} = useEmployeesName(projectId, debouncedSearch);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (value && !search) {
|
||||||
|
const found = employees?.data?.find((emp) => emp.id === value);
|
||||||
|
if (found) setSearch(found.firstName + " " + found.lastName);
|
||||||
|
}
|
||||||
|
}, [value, employees]);
|
||||||
|
|
||||||
|
const handleSelect = (employee) => {
|
||||||
|
onChange(employee.id);
|
||||||
|
setSearch(employee.firstName + " " + employee.lastName);
|
||||||
|
setShowDropdown(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="position-relative">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
ref={ref}
|
||||||
|
className={`form-control form-control-sm`}
|
||||||
|
placeholder="Search employee..."
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSearch(e.target.value);
|
||||||
|
setShowDropdown(true);
|
||||||
|
onChange(""); // Clear previous selection
|
||||||
|
}}
|
||||||
|
onFocus={() => {
|
||||||
|
if (search) setShowDropdown(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{showDropdown && (employees?.data?.length > 0 || isLoading) && (
|
||||||
|
<ul
|
||||||
|
className="list-group position-absolute bg-white w-100 shadow z-3 rounded-none"
|
||||||
|
style={{ maxHeight: 200, overflowY: "auto" }}
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<li className="list-group-item">
|
||||||
|
<a>Searching...</a>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
) : (
|
||||||
|
employees?.data?.map((emp) => (
|
||||||
|
<li
|
||||||
|
key={emp.id}
|
||||||
|
className="list-group-item list-group-item-action"
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
onClick={() => handleSelect(emp)}
|
||||||
|
>
|
||||||
|
{emp.firstName} {emp.lastName}
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && <small className="danger-text">{error.message}</small>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmployeeSearchInput;
|
||||||
20
src/components/common/Error.jsx
Normal file
20
src/components/common/Error.jsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const Error = ({error,close}) => {
|
||||||
|
console.log(error)
|
||||||
|
return (
|
||||||
|
<div className="container text-center py-5">
|
||||||
|
<h1 className="display-4 fw-bold text-danger">{error.statusCode || error?.response?.status
|
||||||
|
}</h1>
|
||||||
|
<h2 className="mb-3">Internal Server Error</h2>
|
||||||
|
<p className="lead">
|
||||||
|
{error.message}
|
||||||
|
</p>
|
||||||
|
<a href="/" className="btn btn-primary btn-sm mt-3" onClick={()=>close()}>
|
||||||
|
Go to Home
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Error
|
||||||
@ -3,27 +3,22 @@ import { cacheData, getCachedData } from "../slices/apiDataManager";
|
|||||||
import { RolesRepository } from "../repositories/MastersRepository";
|
import { RolesRepository } from "../repositories/MastersRepository";
|
||||||
import EmployeeRepository from "../repositories/EmployeeRepository";
|
import EmployeeRepository from "../repositories/EmployeeRepository";
|
||||||
import ProjectRepository from "../repositories/ProjectRepository";
|
import ProjectRepository from "../repositories/ProjectRepository";
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import showToast from "../services/toastService";
|
import showToast from "../services/toastService";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { store } from "../store/store";
|
import { store } from "../store/store";
|
||||||
import { queryClient } from "../layouts/AuthLayout";
|
import { queryClient } from "../layouts/AuthLayout";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Query ---------------------------------------------------------------------------
|
// Query ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const useAllEmployees = (showInactive) => {
|
||||||
export const useAllEmployees = ( showInactive ) =>
|
|
||||||
{
|
|
||||||
const {
|
const {
|
||||||
data = [],
|
data = [],
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
refetch, // optional if you want recall functionality
|
refetch, // optional if you want recall functionality
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ['allEmployee', showInactive],
|
queryKey: ["allEmployee", showInactive],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await EmployeeRepository.getAllEmployeeList(showInactive);
|
const res = await EmployeeRepository.getAllEmployeeList(showInactive);
|
||||||
return res.data;
|
return res.data;
|
||||||
@ -39,9 +34,7 @@ export const useAllEmployees = ( showInactive ) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ManageBucket.jsx
|
// ManageBucket.jsx
|
||||||
export const useEmployees = ( selectedProject ) =>
|
export const useEmployees = (selectedProject) => {
|
||||||
{
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data = [],
|
data = [],
|
||||||
isLoading,
|
isLoading,
|
||||||
@ -50,7 +43,9 @@ export const useEmployees = ( selectedProject ) =>
|
|||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ["employeeListByProject", selectedProject],
|
queryKey: ["employeeListByProject", selectedProject],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await EmployeeRepository.getEmployeeListByproject(selectedProject);
|
const res = await EmployeeRepository.getEmployeeListByproject(
|
||||||
|
selectedProject
|
||||||
|
);
|
||||||
return res.data || res;
|
return res.data || res;
|
||||||
},
|
},
|
||||||
enabled: !!selectedProject,
|
enabled: !!selectedProject,
|
||||||
@ -72,7 +67,7 @@ export const useEmployeeRoles = (employeeId) => {
|
|||||||
isLoading: loading,
|
isLoading: loading,
|
||||||
error,
|
error,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ['employeeRoles', employeeId],
|
queryKey: ["employeeRoles", employeeId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await RolesRepository.getEmployeeRoles(employeeId);
|
const res = await RolesRepository.getEmployeeRoles(employeeId);
|
||||||
return res.data;
|
return res.data;
|
||||||
@ -95,7 +90,7 @@ export const useEmployeesByProject = (projectId) => {
|
|||||||
error,
|
error,
|
||||||
refetch: recallProjectEmplloyee,
|
refetch: recallProjectEmplloyee,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ['projectEmployees', projectId],
|
queryKey: ["projectEmployees", projectId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await ProjectRepository.getEmployeesByProject(projectId);
|
const res = await ProjectRepository.getEmployeesByProject(projectId);
|
||||||
return res.data;
|
return res.data;
|
||||||
@ -112,13 +107,14 @@ export const useEmployeesByProject = (projectId) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// EmployeeList.jsx
|
// EmployeeList.jsx
|
||||||
export const useEmployeesAllOrByProjectId = (showAllEmployees ,projectId,
|
export const useEmployeesAllOrByProjectId = (
|
||||||
showInactive) => {
|
showAllEmployees,
|
||||||
|
projectId,
|
||||||
|
showInactive
|
||||||
|
) => {
|
||||||
const queryKey = showAllEmployees
|
const queryKey = showAllEmployees
|
||||||
? ['allEmployees', showInactive]
|
? ["allEmployees", showInactive]
|
||||||
: ['projectEmployees', projectId, showInactive];
|
: ["projectEmployees", projectId, showInactive];
|
||||||
|
|
||||||
const queryFn = async () => {
|
const queryFn = async () => {
|
||||||
if (showAllEmployees) {
|
if (showAllEmployees) {
|
||||||
@ -139,7 +135,8 @@ export const useEmployeesAllOrByProjectId = (showAllEmployees ,projectId,
|
|||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey,
|
queryKey,
|
||||||
queryFn,
|
queryFn,
|
||||||
enabled:typeof showInactive === "boolean" && (showAllEmployees || !!projectId),
|
enabled:
|
||||||
|
typeof showInactive === "boolean" && (showAllEmployees || !!projectId),
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -151,16 +148,15 @@ export const useEmployeesAllOrByProjectId = (showAllEmployees ,projectId,
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ManageEmployee.jsx
|
// ManageEmployee.jsx
|
||||||
export const useEmployeeProfile = ( employeeId ) =>
|
export const useEmployeeProfile = (employeeId) => {
|
||||||
{
|
|
||||||
const isEnabled = !!employeeId;
|
const isEnabled = !!employeeId;
|
||||||
const {
|
const {
|
||||||
data = null,
|
data = null,
|
||||||
isLoading: loading,
|
isLoading: loading,
|
||||||
error,
|
error,
|
||||||
refetch
|
refetch,
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ['employeeProfile', employeeId],
|
queryKey: ["employeeProfile", employeeId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!employeeId) return null;
|
if (!employeeId) return null;
|
||||||
const res = await EmployeeRepository.getEmployeeProfile(employeeId);
|
const res = await EmployeeRepository.getEmployeeProfile(employeeId);
|
||||||
@ -173,47 +169,64 @@ export const useEmployeeProfile = ( employeeId ) =>
|
|||||||
employee: data,
|
employee: data,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
refetch
|
refetch,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useEmployeesName = (projectId, search) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["employees", projectId, search],
|
||||||
|
queryFn: async() => await EmployeeRepository.getEmployeeName(projectId, search),
|
||||||
|
|
||||||
|
staleTime: 5 * 60 * 1000, // Optional: cache for 5 minutes
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Mutation------------------------------------------------------------------
|
// Mutation------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const useUpdateEmployee = () => {
|
||||||
|
const selectedProject = useSelector(
|
||||||
export const useUpdateEmployee = () =>
|
(store) => store.localVariables.projectId
|
||||||
{
|
);
|
||||||
const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (employeeData) => EmployeeRepository.manageEmployee(employeeData),
|
mutationFn: (employeeData) =>
|
||||||
|
EmployeeRepository.manageEmployee(employeeData),
|
||||||
onSuccess: (_, variables) => {
|
onSuccess: (_, variables) => {
|
||||||
const id = variables.id || variables.employeeId;
|
const id = variables.id || variables.employeeId;
|
||||||
const isAllEmployee = variables.IsAllEmployee;
|
const isAllEmployee = variables.IsAllEmployee;
|
||||||
|
|
||||||
// Cache invalidation
|
// Cache invalidation
|
||||||
queryClient.invalidateQueries( {queryKey:[ 'allEmployees'] });
|
queryClient.invalidateQueries({ queryKey: ["allEmployees"] });
|
||||||
// queryClient.invalidateQueries(['employeeProfile', id]);
|
// queryClient.invalidateQueries(['employeeProfile', id]);
|
||||||
queryClient.invalidateQueries( {queryKey: [ 'projectEmployees' ]} );
|
queryClient.invalidateQueries({ queryKey: ["projectEmployees"] });
|
||||||
queryClient.removeQueries({ queryKey: ["empListByProjectAllocated"] });
|
queryClient.removeQueries({ queryKey: ["empListByProjectAllocated"] });
|
||||||
|
|
||||||
// queryClient.invalidateQueries( {queryKey:[ 'employeeListByProject']} );
|
// queryClient.invalidateQueries( {queryKey:[ 'employeeListByProject']} );
|
||||||
showToast( `Employee ${ id ? 'updated' : 'created' } successfully`, 'success' );
|
showToast(
|
||||||
|
`Employee ${id ? "updated" : "created"} successfully`,
|
||||||
|
"success"
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
const msg = error?.response?.data?.message || error.message || 'Something went wrong';
|
const msg =
|
||||||
showToast(msg, 'error');
|
error?.response?.data?.message ||
|
||||||
|
error.message ||
|
||||||
|
"Something went wrong";
|
||||||
|
showToast(msg, "error");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useSuspendEmployee = ({
|
||||||
|
setIsDeleteModalOpen,
|
||||||
export const useSuspendEmployee = ({ setIsDeleteModalOpen, setemployeeLodaing }) => {
|
setemployeeLodaing,
|
||||||
|
}) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const selectedProject = useSelector((store)=>store.localVariables.projectId)
|
const selectedProject = useSelector(
|
||||||
|
(store) => store.localVariables.projectId
|
||||||
|
);
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (id) => {
|
mutationFn: (id) => {
|
||||||
setemployeeLodaing(true);
|
setemployeeLodaing(true);
|
||||||
@ -221,11 +234,11 @@ export const useSuspendEmployee = ({ setIsDeleteModalOpen, setemployeeLodaing })
|
|||||||
},
|
},
|
||||||
|
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|
||||||
|
|
||||||
// queryClient.invalidateQueries( ['allEmployee',false]);
|
// queryClient.invalidateQueries( ['allEmployee',false]);
|
||||||
queryClient.invalidateQueries( {queryKey: [ 'projectEmployees' ]} );
|
queryClient.invalidateQueries({ queryKey: ["projectEmployees"] });
|
||||||
queryClient.invalidateQueries( {queryKey:[ 'employeeListByProject' ,selectedProject]} );
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["employeeListByProject", selectedProject],
|
||||||
|
});
|
||||||
showToast("Employee deleted successfully.", "success");
|
showToast("Employee deleted successfully.", "success");
|
||||||
setIsDeleteModalOpen(false);
|
setIsDeleteModalOpen(false);
|
||||||
},
|
},
|
||||||
@ -247,8 +260,11 @@ export const useSuspendEmployee = ({ setIsDeleteModalOpen, setemployeeLodaing })
|
|||||||
|
|
||||||
// Manage Role
|
// Manage Role
|
||||||
|
|
||||||
|
export const useUpdateEmployeeRoles = ({
|
||||||
export const useUpdateEmployeeRoles = ({ onClose, resetForm, onSuccessCallback } = {}) => {
|
onClose,
|
||||||
|
resetForm,
|
||||||
|
onSuccessCallback,
|
||||||
|
} = {}) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const mutation = useMutation({
|
const mutation = useMutation({
|
||||||
mutationFn: (updates) => RolesRepository.createEmployeeRoles(updates),
|
mutationFn: (updates) => RolesRepository.createEmployeeRoles(updates),
|
||||||
@ -264,7 +280,9 @@ export const useUpdateEmployeeRoles = ({ onClose, resetForm, onSuccessCallback }
|
|||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
const message =
|
const message =
|
||||||
err?.response?.data?.message || err?.message || "Error occurred while updating roles";
|
err?.response?.data?.message ||
|
||||||
|
err?.message ||
|
||||||
|
"Error occurred while updating roles";
|
||||||
showToast(message, "error");
|
showToast(message, "error");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -20,8 +20,12 @@ const cleanFilter = (filter) => {
|
|||||||
return cleaned;
|
return cleaned;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useExpenseList = (
|
||||||
export const useExpenseList = (pageSize, pageNumber, filter, searchString = '') => {
|
pageSize,
|
||||||
|
pageNumber,
|
||||||
|
filter,
|
||||||
|
searchString = ""
|
||||||
|
) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["Expenses", pageNumber, pageSize, filter, searchString],
|
queryKey: ["Expenses", pageNumber, pageSize, filter, searchString],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@ -38,8 +42,6 @@ export const useExpenseList = (pageSize, pageNumber, filter, searchString = '')
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const useExpense = (ExpenseId) => {
|
export const useExpense = (ExpenseId) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["Expense", ExpenseId],
|
queryKey: ["Expense", ExpenseId],
|
||||||
@ -55,13 +57,10 @@ export const useExpenseFilter = () => {
|
|||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["ExpenseFilter"],
|
queryKey: ["ExpenseFilter"],
|
||||||
queryFn: async () =>
|
queryFn: async () =>
|
||||||
await ExpenseRepository.GetExpenseFilter().then(
|
await ExpenseRepository.GetExpenseFilter().then((res) => res.data),
|
||||||
(res) => res.data
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------Mutation---------------------------------------------
|
// ---------------------------Mutation---------------------------------------------
|
||||||
|
|
||||||
export const useCreateExpnse = (onSuccessCallBack) => {
|
export const useCreateExpnse = (onSuccessCallBack) => {
|
||||||
@ -145,7 +144,7 @@ export const useUpdateExpense = (onSuccessCallBack) => {
|
|||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
showToast("Something went wrong.Please try again later.", "error");
|
showToast("Something went wrong.Please try again later.", "error");
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -181,22 +180,24 @@ export const useActionOnExpense = (onSuccessCallBack) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
queryClient.setQueriesData(
|
// queryClient.setQueriesData(
|
||||||
{ queryKey: ["Expense", updatedExpense.id] },
|
// { queryKey: ["Expense", updatedExpense.id] },
|
||||||
(oldData) => {
|
// (oldData) => {
|
||||||
return {
|
// return {
|
||||||
...oldData,
|
// ...oldData,
|
||||||
nextStatus: updatedExpense.nextStatus,
|
// nextStatus: updatedExpense.nextStatus,
|
||||||
status: updatedExpense.status,
|
// status: updatedExpense.status,
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
|
queryClient.invalidateQueries({queryKey:["Expense",updatedExpense.id]})
|
||||||
|
|
||||||
if (onSuccessCallBack) onSuccessCallBack();
|
if (onSuccessCallBack) onSuccessCallBack();
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
showToast(
|
showToast(
|
||||||
error.message || "Something went wrong.Please try again later.",
|
error.response.data.message ||
|
||||||
|
"Something went wrong.Please try again later.",
|
||||||
"error"
|
"error"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -213,7 +214,8 @@ export const useDeleteExpense = () => {
|
|||||||
},
|
},
|
||||||
onSuccess: (data, variables) => {
|
onSuccess: (data, variables) => {
|
||||||
queryClient.setQueryData(["Expenses"], (oldData) => {
|
queryClient.setQueryData(["Expenses"], (oldData) => {
|
||||||
if (!oldData || !oldData.data) return queryClient.invalidateQueries({queryKey:["Expenses"]});
|
if (!oldData || !oldData.data)
|
||||||
|
return queryClient.invalidateQueries({ queryKey: ["Expenses"] });
|
||||||
|
|
||||||
const updatedList = oldData.data.filter(
|
const updatedList = oldData.data.filter(
|
||||||
(expense) => expense.id !== variables.id
|
(expense) => expense.id !== variables.id
|
||||||
@ -228,18 +230,18 @@ export const useDeleteExpense = () => {
|
|||||||
showToast(data.message || "Expense deleted successfully", "success");
|
showToast(data.message || "Expense deleted successfully", "success");
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
showToast(error.message || error.response.message || "Something went wrong.Please try again later.", "error");
|
showToast(
|
||||||
|
error.message ||
|
||||||
|
error.response.message ||
|
||||||
|
"Something went wrong.Please try again later.",
|
||||||
|
"error"
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const useHasAnyPermission = (permissionIdsInput) => {
|
export const useHasAnyPermission = (permissionIdsInput) => {
|
||||||
const permissions = useSelector(
|
const permissions = useSelector((state) => state?.profile?.permissions || []);
|
||||||
(state) => state?.profile?.permissions || []
|
|
||||||
);
|
|
||||||
|
|
||||||
const permissionIds = Array.isArray(permissionIdsInput)
|
const permissionIds = Array.isArray(permissionIdsInput)
|
||||||
? permissionIdsInput
|
? permissionIdsInput
|
||||||
|
|||||||
@ -1,46 +1,33 @@
|
|||||||
import React, {
|
import React, { createContext, useContext, useState, useEffect } from "react";
|
||||||
createContext,
|
import { useForm } from "react-hook-form";
|
||||||
useContext,
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
useState,
|
import { useSelector } from "react-redux";
|
||||||
useRef,
|
|
||||||
useEffect,
|
// Components
|
||||||
} from "react";
|
|
||||||
import {
|
|
||||||
useForm,
|
|
||||||
useFieldArray,
|
|
||||||
FormProvider,
|
|
||||||
useFormContext,
|
|
||||||
Controller,
|
|
||||||
} from "react-hook-form";
|
|
||||||
import ExpenseList from "../../components/Expenses/ExpenseList";
|
import ExpenseList from "../../components/Expenses/ExpenseList";
|
||||||
import ViewExpense from "../../components/Expenses/ViewExpense";
|
import ViewExpense from "../../components/Expenses/ViewExpense";
|
||||||
import Breadcrumb from "../../components/common/Breadcrumb";
|
import Breadcrumb from "../../components/common/Breadcrumb";
|
||||||
import GlobalModel from "../../components/common/GlobalModel";
|
import GlobalModel from "../../components/common/GlobalModel";
|
||||||
import PreviewDocument from "../../components/Expenses/PreviewDocument";
|
import PreviewDocument from "../../components/Expenses/PreviewDocument";
|
||||||
import ManageExpense from "../../components/Expenses/ManageExpense";
|
import ManageExpense from "../../components/Expenses/ManageExpense";
|
||||||
import { useProjectName } from "../../hooks/useProjects";
|
import ExpenseFilterPanel from "../../components/Expenses/ExpenseFilterPanel";
|
||||||
import { useExpenseStatus } from "../../hooks/masterHook/useMaster";
|
|
||||||
import {
|
// Context & Hooks
|
||||||
useEmployees,
|
import { useFab } from "../../Context/FabContext";
|
||||||
useEmployeesAllOrByProjectId,
|
|
||||||
} from "../../hooks/useEmployees";
|
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import DateRangePicker from "../../components/common/DateRangePicker";
|
|
||||||
import SelectMultiple from "../../components/common/SelectMultiple";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import {
|
|
||||||
defaultFilter,
|
|
||||||
SearchSchema,
|
|
||||||
} from "../../components/Expenses/ExpenseSchema";
|
|
||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import {
|
import {
|
||||||
CREATE_EXEPENSE,
|
CREATE_EXEPENSE,
|
||||||
VIEW_ALL_EXPNESE,
|
VIEW_ALL_EXPNESE,
|
||||||
VIEW_SELF_EXPENSE,
|
VIEW_SELF_EXPENSE,
|
||||||
} from "../../utils/constants";
|
} from "../../utils/constants";
|
||||||
import { useFab } from "../../Context/FabContext";
|
|
||||||
import ExpenseFilterPanel from "../../components/Expenses/ExpenseFilterPanel";
|
|
||||||
|
|
||||||
|
// Schema & Defaults
|
||||||
|
import {
|
||||||
|
defaultFilter,
|
||||||
|
SearchSchema,
|
||||||
|
} from "../../components/Expenses/ExpenseSchema";
|
||||||
|
|
||||||
|
// Context
|
||||||
export const ExpenseContext = createContext();
|
export const ExpenseContext = createContext();
|
||||||
export const useExpenseContext = () => {
|
export const useExpenseContext = () => {
|
||||||
const context = useContext(ExpenseContext);
|
const context = useContext(ExpenseContext);
|
||||||
@ -51,95 +38,87 @@ export const useExpenseContext = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ExpensePage = () => {
|
const ExpensePage = () => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const [filters, setFilter] = useState();
|
|
||||||
const [groupBy, setGropBy] = useState("transactionDate");
|
|
||||||
const IsCreatedAble = useHasUserPermission(CREATE_EXEPENSE);
|
|
||||||
const [searchText, setSearchText] = useState("");
|
|
||||||
const selectedProjectId = useSelector(
|
const selectedProjectId = useSelector(
|
||||||
(store) => store.localVariables.projectId
|
(store) => store.localVariables.projectId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [filters, setFilter] = useState();
|
||||||
|
const [groupBy, setGroupBy] = useState("transactionDate");
|
||||||
|
const [searchText, setSearchText] = useState("");
|
||||||
|
|
||||||
const [ManageExpenseModal, setManageExpenseModal] = useState({
|
const [ManageExpenseModal, setManageExpenseModal] = useState({
|
||||||
IsOpen: null,
|
IsOpen: null,
|
||||||
expenseId: null,
|
expenseId: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [viewExpense, setViewExpense] = useState({
|
const [viewExpense, setViewExpense] = useState({
|
||||||
expenseId: null,
|
expenseId: null,
|
||||||
view: false,
|
view: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [ViewDocument, setDocumentView] = useState({
|
const [ViewDocument, setDocumentView] = useState({
|
||||||
IsOpen: false,
|
IsOpen: false,
|
||||||
Image: null,
|
Image: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const IsCreatedAble = useHasUserPermission(CREATE_EXEPENSE);
|
||||||
const IsViewAll = useHasUserPermission(VIEW_ALL_EXPNESE);
|
const IsViewAll = useHasUserPermission(VIEW_ALL_EXPNESE);
|
||||||
const IsViewSelf = useHasUserPermission(VIEW_SELF_EXPENSE);
|
const IsViewSelf = useHasUserPermission(VIEW_SELF_EXPENSE);
|
||||||
const contextValue = {
|
|
||||||
setViewExpense,
|
const { setOffcanvasContent, setShowTrigger } = useFab();
|
||||||
setManageExpenseModal,
|
|
||||||
setDocumentView,
|
|
||||||
};
|
|
||||||
|
|
||||||
const methods = useForm({
|
const methods = useForm({
|
||||||
resolver: zodResolver(SearchSchema),
|
resolver: zodResolver(SearchSchema),
|
||||||
defaultValues: defaultFilter,
|
defaultValues: defaultFilter,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { setOffcanvasContent, setShowTrigger } = useFab();
|
const { reset } = methods;
|
||||||
|
|
||||||
const clearFilter = () => {
|
const clearFilter = () => {
|
||||||
setFilter({
|
setFilter(defaultFilter);
|
||||||
projectIds: [],
|
|
||||||
statusIds: [],
|
|
||||||
createdByIds: [],
|
|
||||||
paidById: [],
|
|
||||||
startDate: null,
|
|
||||||
endDate: null,
|
|
||||||
});
|
|
||||||
reset();
|
reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowTrigger(true);
|
setShowTrigger(true);
|
||||||
|
|
||||||
setOffcanvasContent(
|
setOffcanvasContent(
|
||||||
"Expense Filters",
|
"Expense Filters",
|
||||||
<ExpenseFilterPanel
|
<ExpenseFilterPanel
|
||||||
onApply={(data) => {
|
onApply={setFilter}
|
||||||
setFilter(data);
|
handleGroupBy={setGroupBy}
|
||||||
}}
|
clearFilter={clearFilter}
|
||||||
handleGroupBy={(groupId) => setGropBy(groupId)}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
setOffcanvasContent("", null);
|
|
||||||
setShowTrigger(false);
|
setShowTrigger(false);
|
||||||
|
setOffcanvasContent("", null);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const contextValue = {
|
||||||
|
setViewExpense,
|
||||||
|
setManageExpenseModal,
|
||||||
|
setDocumentView,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExpenseContext.Provider value={contextValue}>
|
<ExpenseContext.Provider value={contextValue}>
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
<Breadcrumb
|
<Breadcrumb data={[{ label: "Home", link: "/" }, { label: "Expense" }]} />
|
||||||
data={[
|
|
||||||
{ label: "Home", link: "/" },
|
{(IsViewAll || IsViewSelf) ? (
|
||||||
{ label: "Expense", link: null },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
{IsViewAll || IsViewSelf ? (
|
|
||||||
<>
|
<>
|
||||||
<div className="card my-1 px-0">
|
<div className="card my-1">
|
||||||
<div className="card-body py-2 px-3">
|
<div className="card-body py-2 px-3">
|
||||||
<div className="row align-items-center">
|
<div className="row align-items-center">
|
||||||
<div className="col-12 col-sm-6 col-md-4">
|
<div className="col-sm-6 col-md-4">
|
||||||
<div className="input-group input-group-sm">
|
<div className="d-flex align-items-center">
|
||||||
<span className="input-group-text" id="search-label">
|
<span className="form-label me-2" id="search-label">Search</span>
|
||||||
Search
|
|
||||||
</span>
|
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
className="form-control"
|
className="form-control form-control-sm w-auto"
|
||||||
placeholder="Search Expense"
|
placeholder="Search Expense"
|
||||||
aria-label="Search"
|
|
||||||
aria-describedby="search-label"
|
aria-describedby="search-label"
|
||||||
value={searchText}
|
value={searchText}
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onChange={(e) => setSearchText(e.target.value)}
|
||||||
@ -147,18 +126,13 @@ const ExpensePage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-12 col-sm-6 col-md-8 text-end mt-2 mt-sm-0">
|
<div className="col-sm-6 col-md-8 text-end mt-2 mt-sm-0">
|
||||||
{IsCreatedAble && (
|
{IsCreatedAble && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
title="Add New Expense"
|
|
||||||
className="p-1 me-2 bg-primary rounded-circle"
|
className="p-1 me-2 bg-primary rounded-circle"
|
||||||
onClick={() =>
|
title="Add New Expense"
|
||||||
setManageExpenseModal({
|
onClick={() => setManageExpenseModal({ IsOpen: true, expenseId: null })}
|
||||||
IsOpen: true,
|
|
||||||
expenseId: null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<i className="bx bx-plus fs-4 text-white"></i>
|
<i className="bx bx-plus fs-4 text-white"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -172,45 +146,32 @@ const ExpensePage = () => {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="card text-center py-1">
|
<div className="card text-center py-1">
|
||||||
<i className="fa-solid fa-triangle-exclamation fs-5"></i>
|
<i className="fa-solid fa-triangle-exclamation fs-5" />
|
||||||
<p>
|
<p>Access Denied: You don't have permission to perform this action!</p>
|
||||||
Access Denied: You don't have permission to perform this action. !
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Modals */}
|
||||||
{ManageExpenseModal.IsOpen && (
|
{ManageExpenseModal.IsOpen && (
|
||||||
<GlobalModel
|
<GlobalModel
|
||||||
isOpen={ManageExpenseModal.IsOpen}
|
isOpen
|
||||||
size="lg"
|
size="lg"
|
||||||
closeModal={() =>
|
closeModal={() => setManageExpenseModal({ IsOpen: null, expenseId: null })}
|
||||||
setManageExpenseModal({
|
|
||||||
IsOpen: null,
|
|
||||||
expenseId: null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<ManageExpense
|
<ManageExpense
|
||||||
key={ManageExpenseModal.expenseId ?? "new"}
|
key={ManageExpenseModal.expenseId ?? "new"}
|
||||||
expenseToEdit={ManageExpenseModal.expenseId}
|
expenseToEdit={ManageExpenseModal.expenseId}
|
||||||
closeModal={() =>
|
closeModal={() => setManageExpenseModal({ IsOpen: null, expenseId: null })}
|
||||||
setManageExpenseModal({ IsOpen: null, expenseId: null })
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</GlobalModel>
|
</GlobalModel>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{viewExpense.view && (
|
{viewExpense.view && (
|
||||||
<GlobalModel
|
<GlobalModel
|
||||||
isOpen={viewExpense.view}
|
isOpen
|
||||||
size="lg"
|
size="lg"
|
||||||
modalType="top"
|
modalType="top"
|
||||||
closeModal={() =>
|
closeModal={() => setViewExpense({ expenseId: null, view: false })}
|
||||||
setViewExpense({
|
|
||||||
expenseId: null,
|
|
||||||
view: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<ViewExpense ExpenseId={viewExpense.expenseId} />
|
<ViewExpense ExpenseId={viewExpense.expenseId} />
|
||||||
</GlobalModel>
|
</GlobalModel>
|
||||||
@ -218,9 +179,9 @@ const ExpensePage = () => {
|
|||||||
|
|
||||||
{ViewDocument.IsOpen && (
|
{ViewDocument.IsOpen && (
|
||||||
<GlobalModel
|
<GlobalModel
|
||||||
|
isOpen
|
||||||
size="lg"
|
size="lg"
|
||||||
key={ViewDocument.IsOpen ?? "new"}
|
key={ViewDocument.Image ?? "doc"}
|
||||||
isOpen={ViewDocument.IsOpen}
|
|
||||||
closeModal={() => setDocumentView({ IsOpen: false, Image: null })}
|
closeModal={() => setDocumentView({ IsOpen: false, Image: null })}
|
||||||
>
|
>
|
||||||
<PreviewDocument imageUrl={ViewDocument.Image} />
|
<PreviewDocument imageUrl={ViewDocument.Image} />
|
||||||
|
|||||||
@ -1,17 +1,22 @@
|
|||||||
import { api } from "../utils/axiosClient";
|
import { api } from "../utils/axiosClient";
|
||||||
|
|
||||||
const EmployeeRepository = {
|
const EmployeeRepository = {
|
||||||
getAllEmployeeList:(showInactive)=>api.get(`api/employee/list?showInactive=${showInactive}`),
|
getAllEmployeeList: (showInactive) =>
|
||||||
|
api.get(`api/employee/list?showInactive=${showInactive}`),
|
||||||
getEmployeeListByproject: (projectid) =>
|
getEmployeeListByproject: (projectid) =>
|
||||||
api.get(`/api/employee/list/${projectid}`),
|
api.get(`/api/employee/list/${projectid}`),
|
||||||
searchEmployees: (query) =>
|
searchEmployees: (query) => api.get(`/api/employee/search/${query}`),
|
||||||
api.get(`/api/employee/search/${query}`),
|
manageEmployee: (data) => api.post("/api/employee/manage", data),
|
||||||
manageEmployee: (data) =>
|
|
||||||
api.post("/api/employee/manage", data),
|
|
||||||
updateEmployee: (id, data) => api.put(`/users/${id}`, data),
|
updateEmployee: (id, data) => api.put(`/users/${id}`, data),
|
||||||
// deleteEmployee: ( id ) => api.delete( `/users/${ id }` ),
|
// deleteEmployee: ( id ) => api.delete( `/users/${ id }` ),
|
||||||
getEmployeeProfile: (id) => api.get(`/api/employee/profile/get/${id}`),
|
getEmployeeProfile: (id) => api.get(`/api/employee/profile/get/${id}`),
|
||||||
deleteEmployee:(id)=>api.delete(`/api/employee/${id}`)
|
deleteEmployee: (id) => api.delete(`/api/employee/${id}`),
|
||||||
|
getEmployeeName: (projectId, search) =>
|
||||||
|
api.get(
|
||||||
|
`/api/Employee/basic${projectId ? `?projectId=${projectId}` : ""}${
|
||||||
|
search ? `${projectId ? "&" : "?"}searchString=${search}` : ""
|
||||||
|
}`
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default EmployeeRepository;
|
export default EmployeeRepository;
|
||||||
|
|||||||
@ -44,7 +44,7 @@ axiosClient.interceptors.response.use(
|
|||||||
const originalRequest = error.config;
|
const originalRequest = error.config;
|
||||||
|
|
||||||
// Skip retry for public requests or already retried ones
|
// Skip retry for public requests or already retried ones
|
||||||
if (!originalRequest || originalRequest._retry || originalRequest.authRequired === false) {
|
if (!originalRequest && originalRequest._retry || originalRequest.authRequired === false) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user