feat: add appointment date range filter

This commit is contained in:
Kailasdevdas
2026-05-13 14:20:51 +05:30
parent 988fbd28f1
commit 6889137164
3 changed files with 148 additions and 45 deletions
@@ -143,18 +143,51 @@ export const getAppointments = async (req, res) => {
const page = parseInt(req.query.page) || 1; const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10; const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit; const skip = (page - 1) * limit;
const { date, search } = req.query;
const { date, startDate, endDate, search } = req.query;
const where = {}; const where = {};
if (date) { const hasSingleDate = date && date.trim() !== "";
const hasRange =
(startDate && startDate.trim() !== "") ||
(endDate && endDate.trim() !== "");
if (hasSingleDate) {
const start = new Date(date); const start = new Date(date);
start.setHours(0, 0, 0, 0);
const end = new Date(date); const end = new Date(date);
end.setDate(end.getDate() + 1); end.setHours(23, 59, 59, 999);
where.date = { gte: start, lt: end };
where.date = {
gte: start,
lte: end,
};
} }
if (search) { if (!hasSingleDate && hasRange) {
const dateFilter = {};
if (startDate && startDate.trim() !== "") {
const start = new Date(startDate);
start.setHours(0, 0, 0, 0);
dateFilter.gte = start;
}
if (endDate && endDate.trim() !== "") {
const end = new Date(endDate);
end.setHours(23, 59, 59, 999);
dateFilter.lte = end;
}
where.date = dateFilter;
}
if (search && search.trim() !== "") {
where.OR = [ where.OR = [
{ name: { contains: search, mode: "insensitive" } }, { name: { contains: search, mode: "insensitive" } },
{ mobileNumber: { contains: search } }, { mobileNumber: { contains: search } },
@@ -165,24 +198,39 @@ export const getAppointments = async (req, res) => {
const [appointments, total] = await Promise.all([ const [appointments, total] = await Promise.all([
prisma.appointment.findMany({ prisma.appointment.findMany({
where, where,
include: { doctor: true, department: true }, include: {
orderBy: { createdAt: "desc" }, doctor: true,
department: true,
},
orderBy: {
createdAt: "desc",
},
skip, skip,
take: limit, take: limit,
}), }),
prisma.appointment.count({ where }),
prisma.appointment.count({
where,
}),
]); ]);
res.status(200).json({ res.status(200).json({
success: true, success: true,
data: appointments, data: appointments,
pagination: { total, page, limit, totalPages: Math.ceil(total / limit) }, pagination: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
}); });
} catch (error) { } catch (error) {
console.error(error); console.error(error);
res
.status(500) res.status(500).json({
.json({ success: false, message: "Failed to fetch appointments" }); success: false,
message: "Failed to fetch appointments",
});
} }
}; };
+4
View File
@@ -4,12 +4,16 @@ export const getAppointmentsApi = async (
page = 1, page = 1,
limit = 10, limit = 10,
date = "", date = "",
startDate = "",
endDate = "",
search = "", search = "",
) => { ) => {
const params = new URLSearchParams({ const params = new URLSearchParams({
page: String(page), page: String(page),
limit: String(limit), limit: String(limit),
...(date && { date }), ...(date && { date }),
...(startDate && { startDate }),
...(endDate && { endDate }),
...(search && { search }), ...(search && { search }),
}); });
const res = await apiClient.get(`/appointments/getall?${params}`); const res = await apiClient.get(`/appointments/getall?${params}`);
+84 -33
View File
@@ -40,7 +40,8 @@ export default function AppointmentPage() {
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const [filterDoctor, setFilterDoctor] = useState(""); const [filterDoctor, setFilterDoctor] = useState("");
const [filterDate, setFilterDate] = useState(""); const [filterDate, setFilterDate] = useState("");
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
const [viewOpen, setViewOpen] = useState(false); const [viewOpen, setViewOpen] = useState(false);
const [viewData, setViewData] = useState<any>(null); const [viewData, setViewData] = useState<any>(null);
@@ -56,6 +57,8 @@ export default function AppointmentPage() {
currentPage, currentPage,
itemsPerPage, itemsPerPage,
filterDate, filterDate,
startDate,
endDate,
searchText, searchText,
); );
setAppointments(res?.data || []); setAppointments(res?.data || []);
@@ -66,7 +69,7 @@ export default function AppointmentPage() {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [currentPage, itemsPerPage, filterDate, searchText]); }, [currentPage, itemsPerPage, filterDate, startDate, endDate, searchText]);
useEffect(() => { useEffect(() => {
fetchAll(); fetchAll();
@@ -116,39 +119,87 @@ export default function AppointmentPage() {
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4"> <div className="flex flex-col md:flex-row md:justify-between md:items-center gap-4">
<h1 className="text-3xl font-bold">Appointments</h1> <h1 className="text-3xl font-bold">Appointments</h1>
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-4 items-end">
<Input <div className="flex flex-col gap-1">
placeholder="Search name / phone..." <label className="text-xs font-medium text-muted-foreground">
value={searchText} Search
onChange={(e) => { </label>
setSearchText(e.target.value); <Input
setCurrentPage(1); placeholder="Search name / phone..."
}} value={searchText}
className="w-[220px] text-base" onChange={(e) => {
/> setSearchText(e.target.value);
setCurrentPage(1);
}}
className="w-[220px] text-base"
/>
</div>
<Input <div className="flex flex-col gap-1">
type="date" <label className="text-xs font-medium text-muted-foreground">
value={filterDate} Date
onChange={(e) => { </label>
setFilterDate(e.target.value); <Input
setCurrentPage(1); type="date"
}} value={filterDate}
className="w-[160px] text-base" onChange={(e) => {
/> setFilterDate(e.target.value);
setCurrentPage(1);
}}
className="w-[160px] text-base"
disabled={!!startDate || !!endDate}
/>
</div>
<select <div className="flex flex-col gap-1">
value={itemsPerPage} <label className="text-xs font-medium text-muted-foreground">
onChange={(e) => { From
setItemsPerPage(Number(e.target.value)); </label>
setCurrentPage(1); <Input
}} type="date"
className="flex h-10 rounded-md border border-input bg-background px-3 py-2 text-sm focus:ring-2 focus:ring-primary" value={startDate}
> onChange={(e) => {
<option value={5}>5 / page</option> setStartDate(e.target.value);
<option value={10}>10 / page</option> setCurrentPage(1);
<option value={20}>20 / page</option> }}
</select> className="w-[160px] text-base"
disabled={!!filterDate}
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-xs font-medium text-muted-foreground">
To
</label>
<Input
type="date"
value={endDate}
onChange={(e) => {
setEndDate(e.target.value);
setCurrentPage(1);
}}
className="w-[160px] text-base"
disabled={!!filterDate}
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-xs font-medium text-muted-foreground">
Rows
</label>
<select
value={itemsPerPage}
onChange={(e) => {
setItemsPerPage(Number(e.target.value));
setCurrentPage(1);
}}
className="flex h-10 rounded-md border border-input bg-background px-3 py-2 text-sm focus:ring-2 focus:ring-primary"
>
<option value={5}>5 / page</option>
<option value={10}>10 / page</option>
<option value={20}>20 / page</option>
</select>
</div>
<Button <Button
variant="outline" variant="outline"