Skip to content

Commit e202f38

Browse files
authored
Merge pull request #20 from Health-Informatics-UoN/fix_caching
Fix caching & Introduce groups
2 parents 3aa7f37 + 3ce7a49 commit e202f38

14 files changed

Lines changed: 432 additions & 158 deletions

components/ConceptHover.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"use client";
2+
3+
import {
4+
HoverCard,
5+
HoverCardContent,
6+
HoverCardTrigger,
7+
} from "@/components/ui/hover-card";
8+
import { InfoCircledIcon } from "@radix-ui/react-icons";
9+
10+
export function ConceptHover({
11+
conceptName,
12+
relatedConcepts,
13+
}: {
14+
conceptName: string;
15+
relatedConcepts?: { conceptId: string; conceptName: string }[];
16+
}) {
17+
return (
18+
<HoverCard>
19+
<HoverCardTrigger asChild>
20+
<span className="flex items-center text-2xl cursor-pointer">
21+
{conceptName}
22+
<InfoCircledIcon className="ml-1" />
23+
</span>
24+
</HoverCardTrigger>
25+
26+
<HoverCardContent className="w-80">
27+
<div className="space-y-2">
28+
<div className="font-semibold">Related Concepts:</div>
29+
30+
{relatedConcepts?.length ? (
31+
<div className="text-sm max-h-40 overflow-auto space-y-1">
32+
{relatedConcepts.map((c) => (
33+
<div key={c.conceptId} className="flex flex-col">
34+
<span>{c.conceptName}</span>
35+
<span className="text-xs text-muted-foreground">
36+
{c.conceptId}
37+
</span>
38+
</div>
39+
))}
40+
</div>
41+
) : (
42+
<div className="text-sm text-muted-foreground">
43+
No related concepts.
44+
</div>
45+
)}
46+
</div>
47+
</HoverCardContent>
48+
</HoverCard>
49+
);
50+
}

components/ConceptList.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ export default function ConceptList({ concepts }: { concepts: Concept[] }) {
99
const router = useRouter();
1010
const pathname = usePathname();
1111
const searchParams = useSearchParams();
12-
const selectedConcepts = searchParams.getAll("conceptId");
12+
const selectedConcepts = searchParams.getAll("conceptId").sort();
1313

1414
const onSelect = (conceptId: string) => {
15-
const currentParams = searchParams.getAll("conceptId");
15+
const currentParams = searchParams.getAll("conceptId").sort();
1616

1717
let nextConceptIds: string[];
1818

@@ -35,8 +35,8 @@ export default function ConceptList({ concepts }: { concepts: Concept[] }) {
3535

3636
// Reset page number
3737
params.set("page", "1");
38-
39-
router.replace(`${pathname}?${params.toString()}`);
38+
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
39+
router.refresh();
4040
};
4141
const clearSelection = () => {
4242
router.replace(pathname);

components/DomainSelect.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export default function DomainSelect({ domain }: { domain: string }) {
2222
}
2323

2424
// Keep selected concepts
25-
const conceptIds = searchParams.getAll("conceptId");
25+
const conceptIds = searchParams.getAll("conceptId").sort();
2626
conceptIds.forEach((id) => params.append("conceptId", id));
2727

2828
// Reset page number

components/NoteCard.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
CollapsibleTrigger,
1515
} from "@/components/ui/collapsible";
1616
import { Badge } from "@/components/ui/badge";
17-
import { ArrowRightIcon, ChevronDown } from "lucide-react";
17+
import { ChevronDownIcon } from "@radix-ui/react-icons";
1818
import Link from 'next/link'
1919
export default function NoteCard({
2020
note,
@@ -47,7 +47,7 @@ export default function NoteCard({
4747
<CollapsibleTrigger asChild>
4848
<Button variant="outline" className="group w-full bg-transparent">
4949
Description
50-
<ChevronDown className="ml-auto group-data-[state=open]:rotate-180" />
50+
<ChevronDownIcon className="ml-auto group-data-[state=open]:rotate-180" />
5151
</Button>
5252
</CollapsibleTrigger>
5353
<CollapsibleContent className="flex flex-col items-start gap-2 p-2.5 pt-0 text-sm">

components/NotesList.tsx

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,25 @@ import { Note } from "@/types/OmopTables";
33
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
44
import { getMultiplePmcArticles } from "@/lib/services/pmcService";
55
import { Badge } from "@/components/ui/badge";
6-
import { ScrollArea, ScrollBar } from "./ui/scroll-area";
7-
6+
import { ConceptHover } from "./ConceptHover";
87
export default async function NotesList({
8+
relatedConcepts,
99
notes,
10-
conceptIds,
11-
conceptName,
10+
conceptDetails,
1211
}: {
12+
relatedConcepts: Array<{
13+
inputConceptId: string;
14+
groupConcepts: Array<{
15+
conceptId: string;
16+
conceptName: string;
17+
}>;
18+
}>;
1319
notes: Note[];
14-
conceptIds: Array<string> | null;
15-
conceptName: string | null;
20+
conceptDetails: Array<{
21+
conceptId: string;
22+
conceptName: string;
23+
domain: string;
24+
}>;
1625
}) {
1726
// Sort the PMC IDs for caching
1827
const pmcids = Array.from(
@@ -22,60 +31,79 @@ export default async function NotesList({
2231
.filter((id): id is string => Boolean(id)),
2332
),
2433
).sort();
25-
2634
if (pmcids.length === 0) return;
2735
const articles =
2836
pmcids.length > 0 ? await getMultiplePmcArticles(pmcids) : {};
2937

3038
return (
3139
<Card className="col-span-2">
3240
<CardHeader>
33-
<CardTitle className="text-2xl">
34-
{conceptIds && conceptName
35-
? `Case Reports for ${conceptName}`
36-
: "No term selected"}
41+
<CardTitle className="flex flex-wrap gap-2 items-center">
42+
<span className="text-2xl">Case Reports for: </span>
43+
{conceptDetails.map((c) => {
44+
const related = relatedConcepts.find(
45+
(r) => r.inputConceptId === c.conceptId,
46+
);
47+
return (
48+
<ConceptHover
49+
key={c.conceptId}
50+
conceptName={c.conceptName}
51+
relatedConcepts={related?.groupConcepts}
52+
/>
53+
);
54+
})}
3755
</CardTitle>
3856
</CardHeader>
3957

4058
<CardContent>
41-
{!conceptIds ? (
59+
{!conceptDetails ? (
4260
<div className="text-muted-foreground text-center py-10">
4361
Select a concept from the sidebar to view the associated Case
4462
Reports.
4563
</div>
4664
) : notes.length === 0 ? (
4765
<div className="text-muted-foreground text-center py-10">
48-
No notes found for {conceptName}
66+
No notes found for
67+
{conceptDetails.map((c) => c.conceptName).join(", ")}.
4968
</div>
5069
) : (
5170
<div className="grid grid-cols-1 gap-4">
5271
<div className="mb-4 flex items-center gap-2">
5372
<Badge variant="outline" className="bg-sky-100 dark:bg-[#1B3C53]">
5473
Condition
5574
</Badge>
56-
<Badge variant="outline" className="bg-emerald-100 dark:bg-[#3F4F44]">
75+
<Badge
76+
variant="outline"
77+
className="bg-emerald-100 dark:bg-[#3F4F44]"
78+
>
5779
Drug
5880
</Badge>
59-
<Badge variant="outline" className="bg-violet-100 dark:bg-[#49243E]">
81+
<Badge
82+
variant="outline"
83+
className="bg-violet-100 dark:bg-[#49243E]"
84+
>
6085
Procedure
6186
</Badge>
62-
<Badge variant="outline" className="bg-orange-100 dark:bg-amber-800">
87+
<Badge
88+
variant="outline"
89+
className="bg-orange-100 dark:bg-amber-800"
90+
>
6391
Measurement
6492
</Badge>
6593
</div>
66-
<div className="grid grid-cols-1 gap-4 px-1 py-1">
67-
{notes.map((note) => {
68-
const pmcid = note.note_source_value?.match(/PMC(\d+)/)?.[1];
94+
<div className="grid grid-cols-1 gap-4 px-1 py-1">
95+
{notes.map((note) => {
96+
const pmcid = note.note_source_value?.match(/PMC(\d+)/)?.[1];
6997

70-
return (
71-
<NoteCard
72-
key={note.note_id}
73-
note={note}
74-
article={pmcid ? articles[pmcid] : null}
75-
/>
76-
);
77-
})}
78-
</div>
98+
return (
99+
<NoteCard
100+
key={note.note_id}
101+
note={note}
102+
article={pmcid ? articles[pmcid] : null}
103+
/>
104+
);
105+
})}
106+
</div>
79107
</div>
80108
)}
81109
</CardContent>

components/NotesPagination.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ export default function NotesPagination({
3030
params.set("domain", domain);
3131
}
3232
// Get conceptIds and add to URL
33-
const conceptIds = searchParams.getAll("conceptId");
33+
const conceptIds = searchParams.getAll("conceptId").sort();
3434
conceptIds.forEach((id) => params.append("conceptId", id));
35-
35+
3636
// Add the page number
3737
params.set("page", String(newPage));
3838

components/NotesSection.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,20 @@ export default async function NotesSection({
1717
);
1818
}
1919

20-
const { conceptDetails, notes, total } = await getNotesForConcept(
20+
const { conceptDetails, relatedConcepts, notes, total } = await getNotesForConcept(
2121
conceptIds,
2222
page,
2323
);
2424

2525
const pageSize = 10;
2626
const totalPages = Math.ceil(total / pageSize);
27-
2827
return (
2928
<div className="p-4 space-y-4">
3029
<div className="text-sm text-muted-foreground">{total} articles</div>
3130
<NotesList
31+
relatedConcepts={relatedConcepts}
3232
notes={notes}
33-
conceptIds={conceptIds}
34-
conceptName={conceptDetails.map((c) => c.conceptName).join(", ")}
33+
conceptDetails={conceptDetails}
3534
/>
3635
<NotesPagination page={page} totalPages={totalPages} />
3736
</div>

components/ui/hover-card.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"use client"
2+
3+
import * as React from "react"
4+
import { HoverCard as HoverCardPrimitive } from "radix-ui"
5+
6+
import { cn } from "@/lib/utils"
7+
8+
function HoverCard({
9+
...props
10+
}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {
11+
return <HoverCardPrimitive.Root data-slot="hover-card" {...props} />
12+
}
13+
14+
function HoverCardTrigger({
15+
...props
16+
}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {
17+
return (
18+
<HoverCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
19+
)
20+
}
21+
22+
function HoverCardContent({
23+
className,
24+
align = "center",
25+
sideOffset = 4,
26+
...props
27+
}: React.ComponentProps<typeof HoverCardPrimitive.Content>) {
28+
return (
29+
<HoverCardPrimitive.Portal data-slot="hover-card-portal">
30+
<HoverCardPrimitive.Content
31+
data-slot="hover-card-content"
32+
align={align}
33+
sideOffset={sideOffset}
34+
className={cn(
35+
"z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
36+
className
37+
)}
38+
{...props}
39+
/>
40+
</HoverCardPrimitive.Portal>
41+
)
42+
}
43+
44+
export { HoverCard, HoverCardTrigger, HoverCardContent }

0 commit comments

Comments
 (0)