Skip to content

Commit e46c808

Browse files
authored
Merge pull request #21 from Health-Informatics-UoN/concept_filters
filter by note concept
2 parents e202f38 + 820a0d7 commit e46c808

2 files changed

Lines changed: 112 additions & 22 deletions

File tree

components/NoteCard.tsx

Lines changed: 86 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"use client";
2+
23
import { Concept, Note } from "@/types/OmopTables";
34
import { Button } from "@/components/ui/button";
45
import {
@@ -15,26 +16,64 @@ import {
1516
} from "@/components/ui/collapsible";
1617
import { Badge } from "@/components/ui/badge";
1718
import { ChevronDownIcon } from "@radix-ui/react-icons";
18-
import Link from 'next/link'
19+
import Link from "next/link";
20+
import { usePathname, useRouter, useSearchParams } from "next/navigation";
21+
import { buildSearchParams } from "@/lib/helpers";
22+
1923
export default function NoteCard({
2024
note,
2125
article,
2226
}: {
2327
note: Note;
2428
article: any;
2529
}) {
26-
const pmcid = note.note_source_value?.match(/PMC(\d+)/)?.[1];
30+
const router = useRouter();
31+
const pathname = usePathname();
32+
const searchParams = useSearchParams();
2733

34+
const selectedConceptIds = searchParams.getAll("conceptId");
35+
const pmcid = note.note_source_value?.match(/PMC(\d+)/)?.[1];
2836
const uniqueConcepts = Array.from(
29-
new Map((note.concepts || []).map((c: any) => [c.concept_id, c])).values(),
37+
new Map(
38+
(note.concepts || []).map((c: any) => [
39+
String(c.concept_id),
40+
{
41+
...c,
42+
concept_id: String(c.concept_id),
43+
},
44+
]),
45+
).values(),
3046
);
3147

48+
const selectConcept = (conceptId: string) => {
49+
const existing = searchParams.getAll("conceptId");
50+
51+
const nextConceptIds = existing.includes(conceptId)
52+
? existing.filter((id) => id !== conceptId)
53+
: [...existing, conceptId];
54+
55+
const query = buildSearchParams({
56+
conceptIds: nextConceptIds,
57+
domain: searchParams.get("domain"),
58+
page: 1,
59+
});
60+
61+
router.push(`${pathname}?${query}`);
62+
router.refresh();
63+
};
64+
3265
return (
3366
<Card className="bg-zinc-50 ring-foreground/15 dark:bg-neutral-800 mb-5">
3467
<CardHeader>
3568
<CardTitle className="text-lg">
36-
{article.articleUrl && pmcid ? (
37-
<p> <Link href={article.articleUrl} >{article.title}</Link> </p>
69+
{article?.articleUrl && pmcid ? (
70+
<Link
71+
href={article.articleUrl}
72+
target="_blank"
73+
className="hover:underline"
74+
>
75+
{article.title}
76+
</Link>
3877
) : (
3978
<p className="text-sm text-gray-400">Loading article...</p>
4079
)}
@@ -43,13 +82,14 @@ export default function NoteCard({
4382

4483
<CardContent className="flex flex-wrap items-center gap-2 md:flex-row">
4584
{article ? (
46-
<Collapsible className="rounded-md data-[state=open]:bg-muted">
85+
<Collapsible className="w-full rounded-md data-[state=open]:bg-muted">
4786
<CollapsibleTrigger asChild>
48-
<Button variant="outline" className="group w-full bg-transparent">
87+
<Button variant="ghost" className="group w-full bg-transparent">
4988
Description
50-
<ChevronDownIcon className="ml-auto group-data-[state=open]:rotate-180" />
89+
<ChevronDownIcon className="ml-auto transition-transform group-data-[state=open]:rotate-180" />
5190
</Button>
5291
</CollapsibleTrigger>
92+
5393
<CollapsibleContent className="flex flex-col items-start gap-2 p-2.5 pt-0 text-sm">
5494
{article.description}
5595
</CollapsibleContent>
@@ -61,20 +101,44 @@ export default function NoteCard({
61101

62102
<CardFooter className="bg-neutral-50 border-t-gray-200 dark:bg-neutral-900 dark:border-t-neutral-600">
63103
<div className="flex flex-wrap gap-2">
64-
{uniqueConcepts.map((c: Concept) => (
65-
<Badge
66-
variant={"secondary"}
67-
key={c.concept_id}
68-
className={`flex flex-wrap gap-2 text-sm py-0
69-
${c.domain === "Condition" ? "bg-sky-100 dark:bg-[#1B3C53]" : ""}
70-
${c.domain === "Drug" ? "bg-emerald-100 dark:bg-[#3F4F44]" : ""}
71-
${c.domain === "Procedure" ? "bg-violet-100 dark:bg-[#49243E]" : ""}
72-
${c.domain === "Measurement" ? "bg-orange-100 dark:bg-amber-800" : ""}
73-
`}
74-
>
75-
{c.name}
76-
</Badge>
77-
))}
104+
{uniqueConcepts.map((c: Concept) => {
105+
const selected = selectedConceptIds.includes(String(c.concept_id));
106+
107+
return (
108+
<Badge
109+
key={c.concept_id}
110+
variant={"secondary"}
111+
onClick={() => selectConcept(String(c.concept_id))}
112+
className={`
113+
cursor-pointer transition hover:opacity-80
114+
flex flex-wrap gap-2 py-0 text-sm
115+
${selected ? "ring-2 ring-primary" : ""}
116+
${
117+
c.domain === "Condition"
118+
? "bg-sky-100 dark:bg-[#1B3C53]"
119+
: ""
120+
}
121+
${
122+
c.domain === "Drug"
123+
? "bg-emerald-100 dark:bg-[#3F4F44]"
124+
: ""
125+
}
126+
${
127+
c.domain === "Procedure"
128+
? "bg-violet-100 dark:bg-[#49243E]"
129+
: ""
130+
}
131+
${
132+
c.domain === "Measurement"
133+
? "bg-orange-100 dark:bg-amber-800"
134+
: ""
135+
}
136+
`}
137+
>
138+
{c.name}
139+
</Badge>
140+
);
141+
})}
78142
</div>
79143
</CardFooter>
80144
</Card>

lib/helpers.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export function buildSearchParams({
2+
conceptIds = [],
3+
domain,
4+
page = 1,
5+
}: {
6+
conceptIds?: string[];
7+
domain?: string | null;
8+
page?: number;
9+
}) {
10+
const params = new URLSearchParams();
11+
12+
// Set domain
13+
if (domain && domain !== "All") {
14+
params.set("domain", domain);
15+
}
16+
17+
// Sort and add unique conceptIds
18+
[...new Set(conceptIds)]
19+
.sort((a, b) => Number(a) - Number(b))
20+
.forEach((id) => params.append("conceptId", id));
21+
22+
// Set page
23+
params.set("page", String(page));
24+
25+
return params.toString();
26+
}

0 commit comments

Comments
 (0)