diff --git a/src/components/Expenses/ExpenseFilterPanel.jsx b/src/components/Expenses/ExpenseFilterPanel.jsx index dd1a015c..4c47527c 100644 --- a/src/components/Expenses/ExpenseFilterPanel.jsx +++ b/src/components/Expenses/ExpenseFilterPanel.jsx @@ -1,16 +1,16 @@ -// components/Expense/ExpenseFilterPanel.jsx import React, { useEffect } from "react"; import { FormProvider, useForm, Controller } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { defaultFilter, SearchSchema } from "./ExpenseSchema"; -import DateRangePicker from "../common/DateRangePicker"; +import DateRangePicker, { DateRangePicker1 } from "../common/DateRangePicker"; import SelectMultiple from "../common/SelectMultiple"; import { useProjectName } from "../../hooks/useProjects"; import { useExpenseStatus } from "../../hooks/masterHook/useMaster"; import { useEmployeesAllOrByProjectId } from "../../hooks/useEmployees"; import { useSelector } from "react-redux"; +import moment from "moment"; const ExpenseFilterPanel = ({ onApply }) => { const selectedProjectId = useSelector( @@ -51,7 +51,11 @@ const ExpenseFilterPanel = ({ onApply }) => { }; 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(); }; @@ -66,11 +70,17 @@ const ExpenseFilterPanel = ({ onApply }) => {
- */} + +
diff --git a/src/components/Expenses/ExpenseList.jsx b/src/components/Expenses/ExpenseList.jsx index 4cbcce2c..c44be107 100644 --- a/src/components/Expenses/ExpenseList.jsx +++ b/src/components/Expenses/ExpenseList.jsx @@ -11,8 +11,6 @@ import ConfirmModal from "../common/ConfirmModal"; import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useSelector } from "react-redux"; - - const ExpenseList = ({ filters, groupBy = "transactionDate" }) => { const [deletingId, setDeletingId] = useState(null); const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false); @@ -20,7 +18,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => { const IsExpenseEditable = useHasUserPermission(); const IsExpesneApprpve = useHasUserPermission(APPROVE_EXPENSE); const [currentPage, setCurrentPage] = useState(1); - const pageSize = 10; + const pageSize = 20; const { mutate: DeleteExpense, isPending } = useDeleteExpense(); const { data, isLoading, isError, isInitialLoading, error } = useExpenseList( @@ -63,7 +61,9 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => { key = item.status?.displayName || "Unknown"; break; case "paidBy": - key = `${item.paidBy?.firstName ?? ""} ${item.paidBy?.lastName ?? ""}`.trim(); + key = `${item.paidBy?.firstName ?? ""} ${ + item.paidBy?.lastName ?? "" + }`.trim(); break; case "project": key = item.project?.name || "Unknown Project"; @@ -88,20 +88,21 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => { key: "expensesType", label: "Expense Type", getValue: (e) => e.expensesType?.name || "N/A", - align:"text-start", + align: "text-start", }, { key: "paymentMode", label: "Payment Mode", getValue: (e) => e.paymentMode?.name || "N/A", - align:"text-start" + align: "text-start", }, { key: "paidBy", label: "Paid By", - align:"text-start", + align: "text-start", getValue: (e) => - `${e.paidBy?.firstName ?? ""} ${e.paidBy?.lastName ?? ""}`.trim() || "N/A", + `${e.paidBy?.firstName ?? ""} ${e.paidBy?.lastName ?? ""}`.trim() || + "N/A", customRender: (e) => (
{ lastName={e.paidBy?.lastName} /> - {`${e.paidBy?.firstName ?? ""} ${e.paidBy?.lastName ?? ""}`.trim() || "N/A"} + {`${e.paidBy?.firstName ?? ""} ${ + e.paidBy?.lastName ?? "" + }`.trim() || "N/A"}
- ) + ), }, { key: "submitted", label: "Submitted", getValue: (e) => formatUTCToLocalTime(e?.createdAt), - isAlwaysVisible: true + isAlwaysVisible: true, }, { key: "amount", @@ -131,24 +134,30 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => { ), isAlwaysVisible: true, - align: "text-end" + align: "text-end", }, { key: "status", label: "Status", - align:"text-center", + align: "text-center", getValue: (e) => ( - + {e.status?.name || "Unknown"} - ) - } + ), + }, ]; if (isInitialLoading) return ; if (isError) return
{error}
; - 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); return ( @@ -160,7 +169,7 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => { role="dialog" style={{ display: "block", - backgroundColor: "rgba(0,0,0,0.5)" + backgroundColor: "rgba(0,0,0,0.5)", }} aria-hidden="false" > @@ -177,8 +186,11 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => { )}
-
-
+
+
@@ -194,67 +206,80 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => { ) )} - + - {Object.entries(grouped).map(([group, expenses]) => ( - - - - - {expenses.map((expense) => ( - - {expenseColumns.map( - (col) => - (col.isAlwaysVisible || groupBy !== col.key) && ( - - ) - )} - + - ))} - - ))} - {data?.data?.length === 0 && ( + {expenses.map((expense) => ( + + {expenseColumns.map( + (col) => + (col.isAlwaysVisible || groupBy !== col.key) && ( + + ) + )} + + + ))} + + )) + ) : (
Action + Action +
- {IsGroupedByDate ? formatUTCToLocalTime(group) : group} -
- {col.customRender - ? col.customRender(expense) - : col.getValue(expense)} - -
- - setViewExpense({ expenseId: expense.id, view: true }) - } - > - {(expense.status.name === "Draft" || - expense.status.name === "Rejected") && - expense.createdBy.id === SelfId && ( - - setManageExpenseModal({ - IsOpen: true, - expenseId: expense.id - }) - } - > - )} - {expense.status.name === "Draft" && - expense?.createdBy?.id === SelfId && ( - { - setIsDeleteModalOpen(true); - setDeletingId(expense.id); - }} - > - )} -
+ {Object.keys(grouped).length > 0 ? ( + Object.entries(grouped).map(([group, expenses]) => ( + +
+ + {IsGroupedByDate + ? formatUTCToLocalTime(group) + : group} +
+ {col.customRender + ? col.customRender(expense) + : col.getValue(expense)} + +
+ + setViewExpense({ + expenseId: expense.id, + view: true, + }) + } + > + {(expense.status.name === "Draft" || + expense.status.name === "Rejected") && + expense.createdBy.id === SelfId && ( + + setManageExpenseModal({ + IsOpen: true, + expenseId: expense.id, + }) + } + > + )} + {expense.status.name === "Draft" && + expense.createdBy.id === SelfId && ( + { + setIsDeleteModalOpen(true); + setDeletingId(expense.id); + }} + > + )} +
+
No Expense Found @@ -278,4 +303,3 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => { }; export default ExpenseList; - diff --git a/src/components/Expenses/ManageExpense.jsx b/src/components/Expenses/ManageExpense.jsx index 5cbf3faa..4a95e114 100644 --- a/src/components/Expenses/ManageExpense.jsx +++ b/src/components/Expenses/ManageExpense.jsx @@ -23,6 +23,7 @@ import { } from "../../hooks/useExpense"; import ExpenseSkeleton from "./ExpenseSkeleton"; import moment from "moment"; +import DatePicker from "../common/DatePicker"; const ManageExpense = ({ closeModal, expenseToEdit = null }) => { const { @@ -44,6 +45,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { watch, setValue, reset, + control, formState: { errors }, } = useForm({ resolver: zodResolver(schema), @@ -171,7 +173,7 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { } ); 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) { const editPayload = { ...payload, id: data.id }; ExpenseUpdate({ id: data.id, payload: editPayload }); @@ -321,13 +323,18 @@ const ManageExpense = ({ closeModal, expenseToEdit = null }) => { - + /> */} + + {errors.transactionDate && ( {errors.transactionDate.message} diff --git a/src/components/common/DatePicker.jsx b/src/components/common/DatePicker.jsx index 728b14fb..7777f769 100644 --- a/src/components/common/DatePicker.jsx +++ b/src/components/common/DatePicker.jsx @@ -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); - useEffect(() => { - const fp = flatpickr(inputRef.current, { - dateFormat: "Y-m-d", - defaultDate: new Date(), - onChange: (selectedDates, dateStr) => { - if (onDateChange) { - onDateChange(dateStr); // Pass selected date to parent - } - } - }); + const { + field: { onChange, value, ref } + } = useController({ + name, + control + }); - return () => { - // Cleanup flatpickr instance - fp.destroy(); - }; - }, [onDateChange]); + useEffect(() => { + if (inputRef.current) { + flatpickr(inputRef.current, { + dateFormat: "d-m-Y", + allowInput: allowText, + defaultDate: value + ? flatpickr.parseDate(value, "Y-m-d") + : null, + maxDate:maxDate, + onChange: function (selectedDates, dateStr) { + onChange(dateStr); + }, + ...rest + }); + } + }, [inputRef]); return ( -
-
- {/* */} - -
+
+ { + inputRef.current = el; + ref(el); + }} + readOnly={!allowText} + autoComplete="off" + /> + + { + if (inputRef.current && inputRef.current._flatpickr) { + inputRef.current._flatpickr.open(); + } + }} + > + +
); }; diff --git a/src/components/common/DateRangePicker.jsx b/src/components/common/DateRangePicker.jsx index d2fdb2b3..f2c2f53c 100644 --- a/src/components/common/DateRangePicker.jsx +++ b/src/components/common/DateRangePicker.jsx @@ -1,10 +1,10 @@ import React, { useEffect, useRef } from "react"; - +import { useController, useFormContext } from "react-hook-form"; const DateRangePicker = ({ md, sm, onRangeChange, - DateDifference = 7, + DateDifference = 7, endDateMode = "yesterday", }) => { const inputRef = useRef(null); @@ -12,25 +12,25 @@ const DateRangePicker = ({ useEffect(() => { const endDate = new Date(); if (endDateMode === "yesterday") { - endDate.setDate(endDate.getDate() - 1); + endDate.setDate(endDate.getDate() - 1); } endDate.setHours(0, 0, 0, 0); - const startDate = new Date(endDate); + const startDate = new Date(endDate); startDate.setDate(endDate.getDate() - (DateDifference - 1)); startDate.setHours(0, 0, 0, 0); const fp = flatpickr(inputRef.current, { mode: "range", - dateFormat: "Y-m-d", - altInput: true, - altFormat: "d-m-Y", - defaultDate: [startDate, endDate], - static: false, + dateFormat: "Y-m-d", + altInput: true, + altFormat: "d-m-Y", + defaultDate: [startDate, endDate], + static: false, // appendTo: document.body, clickOpens: true, - maxDate: endDate, + maxDate: endDate, onChange: (selectedDates, dateStr) => { const [startDateString, endDateString] = dateStr.split(" To "); onRangeChange?.({ startDate: startDateString, endDate: endDateString }); @@ -38,8 +38,8 @@ const DateRangePicker = ({ }); onRangeChange?.({ - startDate: startDate.toLocaleDateString("en-CA"), - endDate: endDate.toLocaleDateString("en-CA"), + startDate: startDate.toLocaleDateString("en-CA"), + endDate: endDate.toLocaleDateString("en-CA"), }); return () => { @@ -48,22 +48,99 @@ const DateRangePicker = ({ }, [onRangeChange, DateDifference, endDateMode]); return ( -
- - - -
+
+ + +
); }; 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 ( +
+ { + inputRef.current = el; + ref(el); + }} + readOnly={!allowText} + autoComplete="off" + /> + inputRef.current?._flatpickr?.open()} + > + + +
+ ); +}; diff --git a/src/pages/Expense/ExpensePage.jsx b/src/pages/Expense/ExpensePage.jsx index fadfdf79..d588718b 100644 --- a/src/pages/Expense/ExpensePage.jsx +++ b/src/pages/Expense/ExpensePage.jsx @@ -81,47 +81,13 @@ const ExpensePage = () => { resolver: zodResolver(SearchSchema), 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 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 = () => { setFilter({