diff --git a/frontend/components/chat/patient-cards.tsx b/frontend/components/chat/patient-cards.tsx index f61ab05..59f92e6 100644 --- a/frontend/components/chat/patient-cards.tsx +++ b/frontend/components/chat/patient-cards.tsx @@ -29,7 +29,14 @@ import { Sparkline } from "@/components/chat/sparkline"; import { cn } from "@/lib/utils"; import type { AllergySeverity, LabFlag, Patient, Trend } from "@/lib/patients"; -type BadgeVariant = "default" | "secondary" | "destructive" | "outline"; +type BadgeVariant = + | "default" + | "secondary" + | "destructive" + | "outline" + | "success" + | "info" + | "warning"; type PatientResultProps = { status: "loading" | "ready" | "not-found"; @@ -55,8 +62,8 @@ const labFlagVariant: Record = { }; const statusVariant: Record = { - active: "secondary", - inpatient: "destructive", + active: "success", + inpatient: "info", discharged: "outline", }; diff --git a/frontend/components/patients/patients-view.tsx b/frontend/components/patients/patients-view.tsx index fe5206b..6fd3a63 100644 --- a/frontend/components/patients/patients-view.tsx +++ b/frontend/components/patients/patients-view.tsx @@ -1,6 +1,6 @@ "use client"; -import { Plus, Search, Smartphone } from "lucide-react"; +import { ChevronLeft, ChevronRight, Plus, Search, Smartphone } from "lucide-react"; import { useSearchParams } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -12,13 +12,44 @@ import { PatientDetailSheet } from "@/components/patients/patient-detail-sheet"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { + Pagination, + PaginationContent, + PaginationEllipsis, + PaginationItem, + PaginationLink, +} from "@/components/ui/pagination"; import { listPatients, type Patient } from "@/lib/patients"; +import { cn } from "@/lib/utils"; -type BadgeVariant = "secondary" | "destructive" | "outline"; +// Rows shown per page on the patients table before paginating. +const PAGE_SIZE = 10; +// Page numbers to render, with `null` marking an ellipsis gap. Keeps the first, +// last, and a small window around the current page so the control stays compact +// even with many pages. +function pageWindow(current: number, total: number): (number | null)[] { + if (total <= 7) { + return Array.from({ length: total }, (_, i) => i + 1); + } + const pages: (number | null)[] = [1]; + const start = Math.max(2, current - 1); + const end = Math.min(total - 1, current + 1); + if (start > 2) pages.push(null); + for (let p = start; p <= end; p++) pages.push(p); + if (end < total - 1) pages.push(null); + pages.push(total); + return pages; +} + +type BadgeVariant = "success" | "info" | "outline"; + +// Colour the status for at-a-glance scanning: active patients read as success +// (green), admitted inpatients as info (blue, draws the eye), and discharged as +// a muted outline. const statusVariant: Record = { - active: "secondary", - inpatient: "destructive", + active: "success", + inpatient: "info", discharged: "outline", }; @@ -66,6 +97,17 @@ export function PatientsView() { (p) => !q || p.name.toLowerCase().includes(q) || p.fileNumber.includes(q) ); + // Client-side pagination over the filtered list (10/page). Searching resets to + // the first page (done in the search handler); `page` is clamped at render so a + // shrinking list (filter/refresh) never leaves us past the last page. + const [page, setPage] = useState(1); + const totalPages = Math.max(1, Math.ceil(patients.length / PAGE_SIZE)); + const safePage = Math.min(page, totalPages); + const pageRows = patients.slice( + (safePage - 1) * PAGE_SIZE, + safePage * PAGE_SIZE + ); + const open = (fileNumber: string) => { setSelected(fileNumber); setSheetOpen(true); @@ -100,7 +142,10 @@ export function PatientsView() { setQuery(event.target.value)} + onChange={(event) => { + setQuery(event.target.value); + setPage(1); + }} onKeyDown={(event) => { // Enter opens the top match's record, like picking it from the table. if (event.key === "Enter" && patients.length > 0) { @@ -181,7 +226,7 @@ export function PatientsView() { ) : ( - patients.map((p) => ( + pageRows.map((p) => ( + {!loading && !loadError && patients.length > PAGE_SIZE ? ( +
+

+ {t("patients.pagination.summary", { + from: (safePage - 1) * PAGE_SIZE + 1, + to: Math.min(safePage * PAGE_SIZE, patients.length), + total: patients.length, + })} +

+ + + + setPage(Math.max(1, safePage - 1))} + render={
+ ) : null} + ): React.ReactElement { + return ( +