diff --git a/frontend/components/patients/patient-detail.tsx b/frontend/components/patients/patient-detail.tsx
index b41cfd4..e3df3e4 100644
--- a/frontend/components/patients/patient-detail.tsx
+++ b/frontend/components/patients/patient-detail.tsx
@@ -21,16 +21,6 @@ import { useTranslation } from "react-i18next";
import { Sparkline } from "@/components/chat/sparkline";
import { AttachmentsSection } from "@/components/patients/patient-files";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
-import {
- Timeline,
- TimelineContent,
- TimelineDate,
- TimelineHeader,
- TimelineIndicator,
- TimelineItem,
- TimelineSeparator,
- TimelineTitle,
-} from "@/components/ui/timeline";
import {
type ActivityEntityType,
type ActivityEntry,
@@ -182,36 +172,41 @@ function RecordHistory({ fileNumber }: { fileNumber: string }) {
{t("patientCard.history.empty")}
) : (
- // Every entry is a past, audited event, so mark them all completed
- // (filled indicators) by seeding the active step past the last item.
-
+ // A plain vertical rail so EVERY audited change renders in full — the
+ // icon column draws a connector that stretches to the next entry, and
+ // the flex layout mirrors correctly under RTL.
+
{entries.map((e, i) => {
const Icon = historyIcon[e.entityType] ?? Pencil;
+ const isLast = i === entries.length - 1;
return (
-
-
-
-
- {e.actorName}
-
-
+
+
+
-
-
-
- {e.action}
-
+
+ {!isLast && (
+
+ )}
+
+
+
+ {e.actorName}
+
+
{e.action}
+
+
+
);
})}
-
+
)}
);
diff --git a/frontend/components/patients/patients-view.tsx b/frontend/components/patients/patients-view.tsx
index 0ee517b..35230bd 100644
--- a/frontend/components/patients/patients-view.tsx
+++ b/frontend/components/patients/patients-view.tsx
@@ -13,8 +13,23 @@ import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { ListPagination } from "@/components/ui/list-pagination";
+import {
+ Select,
+ SelectItem,
+ SelectPopup,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
import { listPatients, type Patient } from "@/lib/patients";
+type StatusFilter = "all" | Patient["status"];
+const STATUS_FILTERS: StatusFilter[] = [
+ "all",
+ "active",
+ "inpatient",
+ "discharged",
+];
+
// Rows shown per page on the patients table before paginating.
const PAGE_SIZE = 10;
@@ -32,6 +47,7 @@ const statusVariant: Record = {
export function PatientsView() {
const { t } = useTranslation();
const [query, setQuery] = useState("");
+ const [statusFilter, setStatusFilter] = useState("all");
const [addOpen, setAddOpen] = useState(false);
const [importOpen, setImportOpen] = useState(false);
// Bumped on open so the create dialog remounts with a fresh file # / form.
@@ -69,9 +85,18 @@ export function PatientsView() {
}, []);
const q = query.trim().toLowerCase();
- const patients = allPatients.filter(
- (p) => !q || p.name.toLowerCase().includes(q) || p.fileNumber.includes(q)
- );
+ // Search matches name, MRN, problem/condition labels, and allergy substances;
+ // the status filter narrows to one clinical status.
+ const patients = allPatients.filter((p) => {
+ if (statusFilter !== "all" && p.status !== statusFilter) return false;
+ if (!q) return true;
+ return (
+ p.name.toLowerCase().includes(q) ||
+ p.fileNumber.includes(q) ||
+ p.problems.some((problem) => problem.label.toLowerCase().includes(q)) ||
+ p.allergies.some((a) => a.substance.toLowerCase().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
@@ -133,6 +158,29 @@ export function PatientsView() {
value={query}
/>
+