added flatepicker for dates

This commit is contained in:
pramod mahajan 2025-07-31 08:46:40 +05:30
parent 6fbc0411db
commit b864ed0529
6 changed files with 291 additions and 177 deletions

View File

@ -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 }) => {
<form onSubmit={handleSubmit(onSubmit)} className="p-2 text-start">
<div className="mb-3 w-100">
<label className="form-label">Created Date</label>
<DateRangePicker
{/* <DateRangePicker
onRangeChange={setDateRange}
endDateMode="today"
DateDifference="6"
dateFormat="DD-MM-YYYY"
/> */}
<DateRangePicker1
placeholder="DD-MM-YYYY To DD-MM-YYYY"
startField="startDate"
endField="endDate"
/>
</div>

View File

@ -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) => (
<div className="d-flex align-items-center">
<Avatar
@ -111,16 +112,18 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
lastName={e.paidBy?.lastName}
/>
<span>
{`${e.paidBy?.firstName ?? ""} ${e.paidBy?.lastName ?? ""}`.trim() || "N/A"}
{`${e.paidBy?.firstName ?? ""} ${
e.paidBy?.lastName ?? ""
}`.trim() || "N/A"}
</span>
</div>
)
),
},
{
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) => (
<span className={`badge bg-label-${getColorNameFromHex(e?.status?.color) || "secondary"}`}>
<span
className={`badge bg-label-${
getColorNameFromHex(e?.status?.color) || "secondary"
}`}
>
{e.status?.name || "Unknown"}
</span>
)
}
),
},
];
if (isInitialLoading) return <ExpenseTableSkeleton />;
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);
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" }) => {
)}
<div className="card">
<div className="card-datatable table-responsive">
<div className="dataTables_wrapper no-footer px-2">
<div
className="card-datatable table-responsive "
id="horizontal-example"
>
<div className="dataTables_wrapper no-footer px-2 ">
<table className="table border-top dataTable text-nowrap">
<thead>
<tr>
@ -194,67 +206,80 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
</th>
)
)}
<th>Action</th>
<th className="sticky-action-column bg-white text-center">
Action
</th>
</tr>
</thead>
<tbody>
{Object.entries(grouped).map(([group, expenses]) => (
<React.Fragment key={group}>
<tr className="tr-group text-dark">
<td colSpan={8} className="text-start">
<strong>{IsGroupedByDate ? formatUTCToLocalTime(group) : group}</strong>
</td>
</tr>
{expenses.map((expense) => (
<tr key={expense.id}>
{expenseColumns.map(
(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>
{Object.keys(grouped).length > 0 ? (
Object.entries(grouped).map(([group, expenses]) => (
<React.Fragment key={group}>
<tr className="tr-group text-dark">
<td colSpan={8} className="text-start">
<strong>
{IsGroupedByDate
? formatUTCToLocalTime(group)
: group}
</strong>
</td>
</tr>
))}
</React.Fragment>
))}
{data?.data?.length === 0 && (
{expenses.map((expense) => (
<tr key={expense.id}>
{expenseColumns.map(
(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>
<td colSpan={8} className="text-center py-4">
No Expense Found
@ -278,4 +303,3 @@ const ExpenseList = ({ filters, groupBy = "transactionDate" }) => {
};
export default ExpenseList;

View File

@ -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 }) => {
<label for="transactionDate" className="form-label ">
Transaction Date
</label>
<input
{/* <input
type="date"
className="form-control form-control-sm"
placeholder="YYYY-MM-DD"
id="flatpickr-date"
{...register("transactionDate")}
/>
/> */}
<DatePicker
name="transactionDate"
control={control}
/>
{errors.transactionDate && (
<small className="danger-text">
{errors.transactionDate.message}

View File

@ -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 (
<div className="container mt-3">
<div className="mb-3">
{/* <label htmlFor="flatpickr-single" className="form-label">
Select Date
</label> */}
<input
type="text"
id="flatpickr-single"
className="form-control"
placeholder="YYYY-MM-DD"
ref={inputRef}
/>
</div>
<div className={` position-relative ${className}`}>
<input
type="text"
className="form-control form-control-sm "
placeholder={placeholder}
defaultValue={
value ? flatpickr.formatDate(flatpickr.parseDate(value, "Y-m-d"), "d-m-Y") : ""
}
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={() => {
if (inputRef.current && inputRef.current._flatpickr) {
inputRef.current._flatpickr.open();
}
}}
>
<i className="bx bx-calendar bx-sm fs-5 text-muted"></i>
</span>
</div>
);
};

View File

@ -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 (
<div className={`col-${sm} col-sm-${md} px-1 position-relative`}>
<input
type="text"
className="form-control form-control-sm ps-2 pe-5 me-4"
placeholder="From to End"
id="flatpickr-range"
ref={inputRef}
/>
<i
className="bx bx-calendar calendar-icon cursor-pointer position-absolute top-50 translate-middle-y "
style={{right:"12px"}}
></i>
</div>
<div className={`col-${sm} col-sm-${md} px-1 position-relative`}>
<input
type="text"
className="form-control form-control-sm ps-2 pe-5 me-4"
placeholder="From to End"
id="flatpickr-range"
ref={inputRef}
/>
<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 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>
);
};

View File

@ -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({