[1.0.0] #19

Merged
ashir merged 80 commits from dev into main 2026-04-30 18:37:18 +00:00
3 changed files with 97 additions and 53 deletions
Showing only changes of commit 65e6413129 - Show all commits
@@ -71,26 +71,49 @@ export const createAppointment = async (req, res) => {
export const getAppointments = async (req, res) => {
try {
const appointments = await prisma.appointment.findMany({
include: {
doctor: true,
department: true,
},
orderBy: {
createdAt: "desc",
},
});
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
const { date, search } = req.query;
const where = {};
if (date) {
const start = new Date(date);
const end = new Date(date);
end.setDate(end.getDate() + 1);
where.date = { gte: start, lt: end };
}
if (search) {
where.OR = [
{ name: { contains: search, mode: "insensitive" } },
{ mobileNumber: { contains: search } },
{ email: { contains: search, mode: "insensitive" } },
];
}
const [appointments, total] = await Promise.all([
prisma.appointment.findMany({
where,
include: { doctor: true, department: true },
orderBy: { createdAt: "desc" },
skip,
take: limit,
}),
prisma.appointment.count({ where }),
]);
res.status(200).json({
success: true,
data: appointments,
pagination: { total, page, limit, totalPages: Math.ceil(total / limit) },
});
} catch (error) {
console.error(error);
res.status(500).json({
success: false,
message: "Failed to fetch appointments",
});
res
.status(500)
.json({ success: false, message: "Failed to fetch appointments" });
}
};
+13 -2
View File
@@ -1,7 +1,18 @@
import apiClient from "@/api/client";
export const getAppointmentsApi = async () => {
const res = await apiClient.get("/appointments/getall");
export const getAppointmentsApi = async (
page = 1,
limit = 10,
date = "",
search = "",
) => {
const params = new URLSearchParams({
page: String(page),
limit: String(limit),
...(date && { date }),
...(search && { search }),
});
const res = await apiClient.get(`/appointments/getall?${params}`);
return res.data;
};
+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">