feat: add appointment date range filter
This commit is contained in:
@@ -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,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}`);
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user