feat: server-side pagination, search, and date filter for appointments

This commit is contained in:
Kailasdevdas
2026-04-24 11:40:14 +05:30
parent 51d604d6ee
commit 65e6413129
3 changed files with 97 additions and 53 deletions
+40 -30
View File
@@ -45,52 +45,46 @@ export default function AppointmentPage() {
const [viewData, setViewData] = useState<any>(null);
const [currentPage, setCurrentPage] = useState(1);
const itemsPerPage = 10;
const [totalPages, setTotalPages] = useState(1);
const [totalItems, setTotalItems] = useState(0);
const [itemsPerPage, setItemsPerPage] = useState(10);
const fetchAll = useCallback(async () => {
setLoading(true);
try {
const res = await getAppointmentsApi();
const res = await getAppointmentsApi(
currentPage,
itemsPerPage,
filterDate,
searchText,
);
setAppointments(res?.data || []);
setTotalPages(res?.pagination?.totalPages || 1);
setTotalItems(res?.pagination?.total || 0);
} catch (err) {
console.error(err);
} finally {
setLoading(false);
}
}, []);
}, [currentPage, itemsPerPage, filterDate, searchText]);
useEffect(() => {
fetchAll();
}, [fetchAll]);
const filteredAppointments = appointments.filter((item) => {
const matchesSearch =
item.name?.toLowerCase().includes(searchText.toLowerCase()) ||
item.mobileNumber?.includes(searchText) ||
item.email?.toLowerCase().includes(searchText.toLowerCase());
const matchesDoctor = filterDoctor
? item.doctor?.name?.toLowerCase().includes(filterDoctor.toLowerCase())
: true;
const matchesDate = filterDate
? new Date(item.date).toISOString().split("T")[0] === filterDate
: true;
return matchesSearch && matchesDoctor && matchesDate;
return matchesDoctor;
});
useEffect(() => {
setCurrentPage(1);
}, [searchText, filterDoctor, filterDate]);
const totalPages = Math.ceil(filteredAppointments.length / itemsPerPage);
const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const currentItems = filteredAppointments.slice(
indexOfFirstItem,
indexOfLastItem,
);
const indexOfFirstItem = (currentPage - 1) * itemsPerPage;
function openView(item: any) {
setViewData(item);
@@ -126,17 +120,36 @@ export default function AppointmentPage() {
<Input
placeholder="Search name / phone..."
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
onChange={(e) => {
setSearchText(e.target.value);
setCurrentPage(1);
}}
className="w-[220px] text-base"
/>
<Input
type="date"
value={filterDate}
onChange={(e) => setFilterDate(e.target.value)}
onChange={(e) => {
setFilterDate(e.target.value);
setCurrentPage(1);
}}
className="w-[160px] text-base"
/>
<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>
<Button
variant="outline"
onClick={fetchAll}
@@ -192,7 +205,7 @@ export default function AppointmentPage() {
<Loader2 className="h-8 w-8 animate-spin mx-auto" />
</TableCell>
</TableRow>
) : currentItems.length === 0 ? (
) : filteredAppointments.length === 0 ? (
<TableRow>
<TableCell
colSpan={6}
@@ -202,7 +215,7 @@ export default function AppointmentPage() {
</TableCell>
</TableRow>
) : (
currentItems.map((item) => (
filteredAppointments.map((item) => (
<TableRow key={item.id} className="hover:bg-muted/50">
<TableCell className="font-mono text-xs">
{item.id}
@@ -260,18 +273,15 @@ export default function AppointmentPage() {
</Table>
</div>
{!loading && filteredAppointments.length > 0 && (
{!loading && totalItems > 0 && (
<div className="flex items-center justify-between px-2 py-6 border-t">
<div className="text-base text-muted-foreground">
Showing{" "}
<span className="font-semibold">{indexOfFirstItem + 1}</span> to{" "}
<span className="font-semibold">
{Math.min(indexOfLastItem, filteredAppointments.length)}
{Math.min(currentPage * itemsPerPage, totalItems)}
</span>{" "}
of{" "}
<span className="font-semibold">
{filteredAppointments.length}
</span>
of <span className="font-semibold">{totalItems}</span>
</div>
<div className="flex items-center gap-6">
<div className="text-base font-semibold">