Refactor_Expenses #321
@ -1,16 +1,16 @@
|
|||||||
// components/Expense/ExpenseFilterPanel.jsx
|
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { FormProvider, useForm, Controller } from "react-hook-form";
|
import { FormProvider, useForm, Controller } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { defaultFilter, SearchSchema } from "./ExpenseSchema";
|
import { defaultFilter, SearchSchema } from "./ExpenseSchema";
|
||||||
|
|
||||||
import DateRangePicker from "../common/DateRangePicker";
|
import DateRangePicker, { DateRangePicker1 } from "../common/DateRangePicker";
|
||||||
import SelectMultiple from "../common/SelectMultiple";
|
import SelectMultiple from "../common/SelectMultiple";
|
||||||
|
|
||||||
import { useProjectName } from "../../hooks/useProjects";
|
import { useProjectName } from "../../hooks/useProjects";
|
||||||
import { useExpenseStatus } from "../../hooks/masterHook/useMaster";
|
import { useExpenseStatus } from "../../hooks/masterHook/useMaster";
|
||||||
import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees";
|
import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
const ExpenseFilterPanel = ({ onApply }) => {
|
const ExpenseFilterPanel = ({ onApply }) => {
|
||||||
const selectedProjectId = useSelector(
|
const selectedProjectId = useSelector(
|
||||||
@ -51,7 +51,11 @@ const ExpenseFilterPanel = ({ onApply }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = (data) => {
|
const onSubmit = (data) => {
|
||||||
onApply(data);
|
onApply({
|
||||||
|
...data,
|
||||||
|
startDate: moment.utc(data.startDate, "DD-MM-YYYY").toISOString(),
|
||||||
|
endDate: moment.utc(data.endDate, "DD-MM-YYYY").toISOString(),
|
||||||
|
});
|
||||||
closePanel();
|
closePanel();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -66,11 +70,17 @@ const ExpenseFilterPanel = ({ onApply }) => {
|
|||||||
<form onSubmit={handleSubmit(onSubmit)} className="p-2 text-start">
|
<form onSubmit={handleSubmit(onSubmit)} className="p-2 text-start">
|
||||||
<div className="mb-3 w-100">
|
<div className="mb-3 w-100">
|
||||||
<label className="form-label">Created Date</label>
|
<label className="form-label">Created Date</label>
|
||||||
<DateRangePicker
|
{/* <DateRangePicker
|
||||||
onRangeChange={setDateRange}
|
onRangeChange={setDateRange}
|
||||||
endDateMode="today"
|
endDateMode="today"
|
||||||
DateDifference="6"
|
DateDifference="6"
|
||||||
dateFormat="DD-MM-YYYY"
|
dateFormat="DD-MM-YYYY"
|
||||||
|
/> */}
|
||||||
|
|
||||||
|
<DateRangePicker1
|
||||||
|
placeholder="DD-MM-YYYY To DD-MM-YYYY"
|
||||||
|
startField="startDate"
|
||||||
|
endField="endDate"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -11,8 +11,6 @@ import ConfirmModal from "../common/ConfirmModal";
|
|||||||
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
|
const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
|
||||||
const [deletingId, setDeletingId] = useState(null);
|
const [deletingId, setDeletingId] = useState(null);
|
||||||
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
@ -20,7 +18,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
|
|||||||
const IsExpenseEditable = useHasUserPermission();
|
const IsExpenseEditable = useHasUserPermission();
|
||||||
const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE);
|
const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const pageSize = 10;
|
const pageSize = 20;
|
||||||
|
|
||||||
const { mutate: DeleteExpense, isPending } = useDeleteExpense();
|
const { mutate: DeleteExpense, isPending } = useDeleteExpense();
|
||||||
const { data, isLoading, isError, isInitialLoading, error } = useExpenseList(
|
const { data, isLoading, isError, isInitialLoading, error } = useExpenseList(
|
||||||
@ -63,7 +61,9 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
|
|||||||
key = item.status?.displayName || "Unknown";
|
key = item.status?.displayName || "Unknown";
|
||||||
break;
|
break;
|
||||||
case "paidBy":
|
case "paidBy":
|
||||||
key = `${item.paidBy?.firstName ?? ""} ${item.paidBy?.lastName ?? ""}`.trim();
|
key = `${item.paidBy?.firstName ?? ""} ${
|
||||||
|
item.paidBy?.lastName ?? ""
|
||||||
|
}`.trim();
|
||||||
break;
|
break;
|
||||||
case "project":
|
case "project":
|
||||||
key = item.project?.name || "Unknown Project";
|
key = item.project?.name || "Unknown Project";
|
||||||
@ -88,20 +88,21 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
|
|||||||
key: "expensesType",
|
key: "expensesType",
|
||||||
label: "Expense Type",
|
label: "Expense Type",
|
||||||
getValue: (e) => e.expensesType?.name || "N/A",
|
getValue: (e) => e.expensesType?.name || "N/A",
|
||||||
align:"text-start",
|
align: "text-start",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "paymentMode",
|
key: "paymentMode",
|
||||||
label: "Payment Mode",
|
label: "Payment Mode",
|
||||||
getValue: (e) => e.paymentMode?.name || "N/A",
|
getValue: (e) => e.paymentMode?.name || "N/A",
|
||||||
align:"text-start"
|
align: "text-start",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "paidBy",
|
key: "paidBy",
|
||||||
label: "Paid By",
|
label: "Paid By",
|
||||||
align:"text-start",
|
align: "text-start",
|
||||||
getValue: (e) =>
|
getValue: (e) =>
|
||||||
`${e.paidBy?.firstName ?? ""} ${e.paidBy?.lastName ?? ""}`.trim() || "N/A",
|
`${e.paidBy?.firstName ?? ""} ${e.paidBy?.lastName ?? ""}`.trim() ||
|
||||||
|
"N/A",
|
||||||
customRender: (e) => (
|
customRender: (e) => (
|
||||||
<div className="d-flex align-items-center">
|
<div className="d-flex align-items-center">
|
||||||
<Avatar
|
<Avatar
|
||||||
@ -111,16 +112,18 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
|
|||||||
lastName={e.paidBy?.lastName}
|
lastName={e.paidBy?.lastName}
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
{`${e.paidBy?.firstName ?? ""} ${e.paidBy?.lastName ?? ""}`.trim() || "N/A"}
|
{`${e.paidBy?.firstName ?? ""} ${
|
||||||
|
e.paidBy?.lastName ?? ""
|
||||||
|
}`.trim() || "N/A"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "submitted",
|
key: "submitted",
|
||||||
label: "Submitted",
|
label: "Submitted",
|
||||||
getValue: (e) => formatUTCToLocalTime(e?.createdAt),
|
getValue: (e) => formatUTCToLocalTime(e?.createdAt),
|
||||||
isAlwaysVisible: true
|
isAlwaysVisible: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "amount",
|
key: "amount",
|
||||||
@ -131,24 +134,30 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
|
|||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
isAlwaysVisible: true,
|
isAlwaysVisible: true,
|
||||||
align: "text-end"
|
align: "text-end",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "status",
|
key: "status",
|
||||||
label: "Status",
|
label: "Status",
|
||||||
align:"text-center",
|
align: "text-center",
|
||||||
getValue: (e) => (
|
getValue: (e) => (
|
||||||
<span className={`badge bg-label-${getColorNameFromHex(e?.status?.color) || "secondary"}`}>
|
<span
|
||||||
|
className={`badge bg-label-${
|
||||||
|
getColorNameFromHex(e?.status?.color) || "secondary"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{e.status?.name || "Unknown"}
|
{e.status?.name || "Unknown"}
|
||||||
</span>
|
</span>
|
||||||
)
|
),
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (isInitialLoading) return <ExpenseTableSkeleton />;
|
if (isInitialLoading) return <ExpenseTableSkeleton />;
|
||||||
if (isError) return <div>{error}</div>;
|
if (isError) return <div>{error}</div>;
|
||||||
|
|
||||||
const grouped = groupBy ? groupByField(data?.data ?? [], groupBy) : { All: data?.data ?? [] };
|
const grouped = groupBy
|
||||||
|
? groupByField(data?.data ?? [], groupBy)
|
||||||
|
: { All: data?.data ?? [] };
|
||||||
const IsGroupedByDate = ["transactionDate", "createdAt"].includes(groupBy);
|
const IsGroupedByDate = ["transactionDate", "createdAt"].includes(groupBy);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -160,7 +169,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
|
|||||||
role="dialog"
|
role="dialog"
|
||||||
style={{
|
style={{
|
||||||
display: "block",
|
display: "block",
|
||||||
backgroundColor: "rgba(0,0,0,0.5)"
|
backgroundColor: "rgba(0,0,0,0.5)",
|
||||||
}}
|
}}
|
||||||
aria-hidden="false"
|
aria-hidden="false"
|
||||||
>
|
>
|
||||||
@ -177,8 +186,11 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-datatable table-responsive">
|
<div
|
||||||
<div className="dataTables_wrapper no-footer px-2">
|
className="card-datatable table-responsive "
|
||||||
|
id="horizontal-example"
|
||||||
|
>
|
||||||
|
<div className="dataTables_wrapper no-footer px-2 ">
|
||||||
<table className="table border-top dataTable text-nowrap">
|
<table className="table border-top dataTable text-nowrap">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -194,67 +206,80 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
|
|||||||
</th>
|
</th>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
<th>Action</th>
|
<th className="sticky-action-column bg-white text-center">
|
||||||
|
Action
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{Object.entries(grouped).map(([group, expenses]) => (
|
{Object.keys(grouped).length > 0 ? (
|
||||||
<React.Fragment key={group}>
|
Object.entries(grouped).map(([group, expenses]) => (
|
||||||
<tr className="tr-group text-dark">
|
<React.Fragment key={group}>
|
||||||
<td colSpan={8} className="text-start">
|
<tr className="tr-group text-dark">
|
||||||
<strong>{IsGroupedByDate ? formatUTCToLocalTime(group) : group}</strong>
|
<td colSpan={8} className="text-start">
|
||||||
</td>
|
<strong>
|
||||||
</tr>
|
{IsGroupedByDate
|
||||||
{expenses.map((expense) => (
|
? formatUTCToLocalTime(group)
|
||||||
<tr key={expense.id}>
|
: group}
|
||||||
{expenseColumns.map(
|
</strong>
|
||||||
(col) =>
|
|
||||||
(col.isAlwaysVisible || groupBy !== col.key) && (
|
|
||||||
<td key={col.key} className={`d-table-cell ${col.align ?? ""}`}>
|
|
||||||
{col.customRender
|
|
||||||
? col.customRender(expense)
|
|
||||||
: col.getValue(expense)}
|
|
||||||
</td>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
<td>
|
|
||||||
<div className="d-flex justify-content-center gap-2">
|
|
||||||
<i
|
|
||||||
className="bx bx-show text-primary cursor-pointer"
|
|
||||||
onClick={() =>
|
|
||||||
setViewExpense({ expenseId: expense.id, view: true })
|
|
||||||
}
|
|
||||||
></i>
|
|
||||||
{(expense.status.name === "Draft" ||
|
|
||||||
expense.status.name === "Rejected") &&
|
|
||||||
expense.createdBy.id === SelfId && (
|
|
||||||
<i
|
|
||||||
className="bx bx-edit text-secondary cursor-pointer"
|
|
||||||
onClick={() =>
|
|
||||||
setManageExpenseModal({
|
|
||||||
IsOpen: true,
|
|
||||||
expenseId: expense.id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
></i>
|
|
||||||
)}
|
|
||||||
{expense.status.name === "Draft" &&
|
|
||||||
expense?.createdBy?.id === SelfId && (
|
|
||||||
<i
|
|
||||||
className="bx bx-trash text-danger cursor-pointer"
|
|
||||||
onClick={() => {
|
|
||||||
setIsDeleteModalOpen(true);
|
|
||||||
setDeletingId(expense.id);
|
|
||||||
}}
|
|
||||||
></i>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
{expenses.map((expense) => (
|
||||||
</React.Fragment>
|
<tr key={expense.id}>
|
||||||
))}
|
{expenseColumns.map(
|
||||||
{data?.data?.length === 0 && (
|
(col) =>
|
||||||
|
(col.isAlwaysVisible || groupBy !== col.key) && (
|
||||||
|
<td
|
||||||
|
key={col.key}
|
||||||
|
className={`d-table-cell ${col.align ?? ""}`}
|
||||||
|
>
|
||||||
|
{col.customRender
|
||||||
|
? col.customRender(expense)
|
||||||
|
: col.getValue(expense)}
|
||||||
|
</td>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<td className="sticky-action-column bg-white">
|
||||||
|
<div className="d-flex justify-content-center gap-2">
|
||||||
|
<i
|
||||||
|
className="bx bx-show text-primary cursor-pointer"
|
||||||
|
onClick={() =>
|
||||||
|
setViewExpense({
|
||||||
|
expenseId: expense.id,
|
||||||
|
view: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
></i>
|
||||||
|
{(expense.status.name === "Draft" ||
|
||||||
|
expense.status.name === "Rejected") &&
|
||||||
|
expense.createdBy.id === SelfId && (
|
||||||
|
<i
|
||||||
|
className="bx bx-edit text-secondary cursor-pointer"
|
||||||
|
onClick={() =>
|
||||||
|
setManageExpenseModal({
|
||||||
|
IsOpen: true,
|
||||||
|
expenseId: expense.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
></i>
|
||||||
|
)}
|
||||||
|
{expense.status.name === "Draft" &&
|
||||||
|
expense.createdBy.id === SelfId && (
|
||||||
|
<i
|
||||||
|
className="bx bx-trash text-danger cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
setIsDeleteModalOpen(true);
|
||||||
|
setDeletingId(expense.id);
|
||||||
|
}}
|
||||||
|
></i>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</React.Fragment>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={8} className="text-center py-4">
|
<td colSpan={8} className="text-center py-4">
|
||||||
No Expense Found
|
No Expense Found
|
||||||
@ -278,4 +303,3 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default ExpenseList;
|
export default ExpenseList;
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
} from "../../hooks/useExpense";
|
} from "../../hooks/useExpense";
|
||||||
import ExpenseSkeleton from "./ExpenseSkeleton";
|
import ExpenseSkeleton from "./ExpenseSkeleton";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
import DatePicker from "../common/DatePicker";
|
||||||
|
|
||||||
const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
||||||
const {
|
const {
|
||||||
@ -44,6 +45,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
watch,
|
watch,
|
||||||
setValue,
|
setValue,
|
||||||
reset,
|
reset,
|
||||||
|
control,
|
||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm({
|
} = useForm({
|
||||||
resolver: zodResolver(schema),
|
resolver: zodResolver(schema),
|
||||||
@ -171,7 +173,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
const onSubmit = (fromdata) => {
|
const onSubmit = (fromdata) => {
|
||||||
let payload = {...fromdata,transactionDate: moment.utc(fromdata.transactionDate, 'YYYY-MM-DD').toISOString()}
|
let payload = {...fromdata,transactionDate: moment.utc(fromdata.transactionDate, 'DD-MM-YYYY').toISOString()}
|
||||||
if (expenseToEdit) {
|
if (expenseToEdit) {
|
||||||
const editPayload = { ...payload, id: data.id };
|
const editPayload = { ...payload, id: data.id };
|
||||||
ExpenseUpdate({ id: data.id, payload: editPayload });
|
ExpenseUpdate({ id: data.id, payload: editPayload });
|
||||||
@ -321,13 +323,18 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => {
|
|||||||
<label for="transactionDate" className="form-label ">
|
<label for="transactionDate" className="form-label ">
|
||||||
Transaction Date
|
Transaction Date
|
||||||
</label>
|
</label>
|
||||||
<input
|
{/* <input
|
||||||
type="date"
|
type="date"
|
||||||
className="form-control form-control-sm"
|
className="form-control form-control-sm"
|
||||||
placeholder="YYYY-MM-DD"
|
placeholder="YYYY-MM-DD"
|
||||||
id="flatpickr-date"
|
id="flatpickr-date"
|
||||||
{...register("transactionDate")}
|
{...register("transactionDate")}
|
||||||
/>
|
/> */}
|
||||||
|
<DatePicker
|
||||||
|
name="transactionDate"
|
||||||
|
control={control}
|
||||||
|
/>
|
||||||
|
|
||||||
{errors.transactionDate && (
|
{errors.transactionDate && (
|
||||||
<small className="danger-text">
|
<small className="danger-text">
|
||||||
{errors.transactionDate.message}
|
{errors.transactionDate.message}
|
||||||
|
@ -1,39 +1,69 @@
|
|||||||
import React, { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
|
import { useController } from "react-hook-form";
|
||||||
|
|
||||||
const DatePicker = ({ onDateChange }) => {
|
|
||||||
|
const DatePicker = ({
|
||||||
|
name,
|
||||||
|
control,
|
||||||
|
placeholder = "DD-MM-YYYY",
|
||||||
|
className = "",
|
||||||
|
allowText = false,
|
||||||
|
maxDate=new Date(),
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
const {
|
||||||
const fp = flatpickr(inputRef.current, {
|
field: { onChange, value, ref }
|
||||||
dateFormat: "Y-m-d",
|
} = useController({
|
||||||
defaultDate: new Date(),
|
name,
|
||||||
onChange: (selectedDates, dateStr) => {
|
control
|
||||||
if (onDateChange) {
|
});
|
||||||
onDateChange(dateStr); // Pass selected date to parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
useEffect(() => {
|
||||||
// Cleanup flatpickr instance
|
if (inputRef.current) {
|
||||||
fp.destroy();
|
flatpickr(inputRef.current, {
|
||||||
};
|
dateFormat: "d-m-Y",
|
||||||
}, [onDateChange]);
|
allowInput: allowText,
|
||||||
|
defaultDate: value
|
||||||
|
? flatpickr.parseDate(value, "Y-m-d")
|
||||||
|
: null,
|
||||||
|
maxDate:maxDate,
|
||||||
|
onChange: function (selectedDates, dateStr) {
|
||||||
|
onChange(dateStr);
|
||||||
|
},
|
||||||
|
...rest
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [inputRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mt-3">
|
<div className={` position-relative ${className}`}>
|
||||||
<div className="mb-3">
|
<input
|
||||||
{/* <label htmlFor="flatpickr-single" className="form-label">
|
type="text"
|
||||||
Select Date
|
className="form-control form-control-sm "
|
||||||
</label> */}
|
placeholder={placeholder}
|
||||||
<input
|
defaultValue={
|
||||||
type="text"
|
value ? flatpickr.formatDate(flatpickr.parseDate(value, "Y-m-d"), "d-m-Y") : ""
|
||||||
id="flatpickr-single"
|
}
|
||||||
className="form-control"
|
ref={(el) => {
|
||||||
placeholder="YYYY-MM-DD"
|
inputRef.current = el;
|
||||||
ref={inputRef}
|
ref(el);
|
||||||
/>
|
}}
|
||||||
</div>
|
readOnly={!allowText}
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span
|
||||||
|
className="position-absolute top-50 end-0 pe-1 translate-middle-y cursor-pointer"
|
||||||
|
onClick={() => {
|
||||||
|
if (inputRef.current && inputRef.current._flatpickr) {
|
||||||
|
inputRef.current._flatpickr.open();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="bx bx-calendar bx-sm fs-5 text-muted"></i>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
|
import { useController, useFormContext } from "react-hook-form";
|
||||||
const DateRangePicker = ({
|
const DateRangePicker = ({
|
||||||
md,
|
md,
|
||||||
sm,
|
sm,
|
||||||
@ -48,22 +48,99 @@ const DateRangePicker = ({
|
|||||||
}, [onRangeChange, DateDifference, endDateMode]);
|
}, [onRangeChange, DateDifference, endDateMode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`col-${sm} col-sm-${md} px-1 position-relative`}>
|
<div className={`col-${sm} col-sm-${md} px-1 position-relative`}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control form-control-sm ps-2 pe-5 me-4"
|
className="form-control form-control-sm ps-2 pe-5 me-4"
|
||||||
placeholder="From to End"
|
placeholder="From to End"
|
||||||
id="flatpickr-range"
|
id="flatpickr-range"
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<i
|
|
||||||
className="bx bx-calendar calendar-icon cursor-pointer position-absolute top-50 translate-middle-y "
|
|
||||||
style={{right:"12px"}}
|
|
||||||
></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<i
|
||||||
|
className="bx bx-calendar calendar-icon cursor-pointer position-absolute top-50 translate-middle-y "
|
||||||
|
style={{ right: "12px" }}
|
||||||
|
></i>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DateRangePicker;
|
export default DateRangePicker;
|
||||||
|
|
||||||
|
export const DateRangePicker1 = ({
|
||||||
|
startField = "startDate",
|
||||||
|
endField = "endDate",
|
||||||
|
label,
|
||||||
|
placeholder = "Select date range",
|
||||||
|
className = "",
|
||||||
|
allowText = false,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
const inputRef = useRef(null);
|
||||||
|
const { control, setValue, getValues } = useFormContext();
|
||||||
|
|
||||||
|
const {
|
||||||
|
field: { ref },
|
||||||
|
} = useController({ name: startField, control });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!inputRef.current || inputRef.current._flatpickr) return;
|
||||||
|
|
||||||
|
const defaultStart = getValues(startField);
|
||||||
|
const defaultEnd = getValues(endField);
|
||||||
|
|
||||||
|
const instance = flatpickr(inputRef.current, {
|
||||||
|
mode: "range",
|
||||||
|
dateFormat: "d-m-Y",
|
||||||
|
allowInput: allowText,
|
||||||
|
defaultDate:
|
||||||
|
defaultStart && defaultEnd
|
||||||
|
? [
|
||||||
|
flatpickr.parseDate(defaultStart, "d-m-Y"),
|
||||||
|
flatpickr.parseDate(defaultEnd, "d-m-Y"),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
onChange: (selectedDates, dateStr, fp) => {
|
||||||
|
if (selectedDates.length === 2) {
|
||||||
|
const [start, end] = selectedDates;
|
||||||
|
const format = (d) => flatpickr.formatDate(d, "d-m-Y");
|
||||||
|
setValue(startField, format(start));
|
||||||
|
setValue(endField, format(end));
|
||||||
|
} else {
|
||||||
|
setValue(startField, "");
|
||||||
|
setValue(endField, "");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...rest,
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => instance.destroy();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const start = getValues(startField);
|
||||||
|
const end = getValues(endField);
|
||||||
|
const formattedValue = start && end ? `${start} To ${end}` : "";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={` position-relative ${className}`}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control form-control-sm"
|
||||||
|
placeholder={placeholder}
|
||||||
|
defaultValue={formattedValue}
|
||||||
|
ref={(el) => {
|
||||||
|
inputRef.current = el;
|
||||||
|
ref(el);
|
||||||
|
}}
|
||||||
|
readOnly={!allowText}
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="position-absolute top-50 end-0 pe-1 translate-middle-y cursor-pointer"
|
||||||
|
onClick={() => inputRef.current?._flatpickr?.open()}
|
||||||
|
>
|
||||||
|
<i className="bx bx-calendar bx-sm fs-5 text-muted"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -81,47 +81,13 @@ const ExpensePage = () => {
|
|||||||
resolver: zodResolver(SearchSchema),
|
resolver: zodResolver(SearchSchema),
|
||||||
defaultValues: defaultFilter,
|
defaultValues: defaultFilter,
|
||||||
});
|
});
|
||||||
const {
|
|
||||||
register,
|
|
||||||
handleSubmit,
|
|
||||||
control,
|
|
||||||
getValues,
|
|
||||||
trigger,
|
|
||||||
setValue,
|
|
||||||
watch,
|
|
||||||
reset,
|
|
||||||
formState: { errors },
|
|
||||||
} = methods;
|
|
||||||
|
|
||||||
const { projectNames, loading: projectLoading } = useProjectName();
|
|
||||||
const { ExpenseStatus, loading: statusLoading, error } = useExpenseStatus();
|
|
||||||
const { employees, loading: empLoading } = useEmployeesAllOrByProjectId(
|
|
||||||
true,
|
|
||||||
selectedProjectId,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
const { setOffcanvasContent, setShowTrigger } = useFab();
|
const { setOffcanvasContent, setShowTrigger } = useFab();
|
||||||
|
|
||||||
const onSubmit = (data) => {
|
|
||||||
setFilter(data);
|
|
||||||
};
|
|
||||||
const isValidDate = (date) => {
|
|
||||||
return date instanceof Date && !isNaN(date);
|
|
||||||
};
|
|
||||||
|
|
||||||
const setDateRange = ({ startDate, endDate }) => {
|
|
||||||
const parsedStart = new Date(startDate);
|
|
||||||
const parsedEnd = new Date(endDate);
|
|
||||||
|
|
||||||
setValue(
|
|
||||||
"startDate",
|
|
||||||
isValidDate(parsedStart) ? parsedStart.toISOString().split("T")[0] : null
|
|
||||||
);
|
|
||||||
setValue(
|
|
||||||
"endDate",
|
|
||||||
isValidDate(parsedEnd) ? parsedEnd.toISOString().split("T")[0] : null
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearFilter = () => {
|
const clearFilter = () => {
|
||||||
setFilter({
|
setFilter({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user