Skip to content

Commit 8157827

Browse files
authored
Merge pull request #23 from Health-Informatics-UoN/ui/updates
add loading transition to concept filtering and try to fix refresh issue
2 parents 759e630 + ff5c046 commit 8157827

4 files changed

Lines changed: 71 additions & 39 deletions

File tree

app/page.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,12 @@ function PageContent({
6464

6565
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
6666
<Card className="p-0 md:col-span-1">
67-
<Suspense fallback={<div>Loading concepts...</div>}>
6867
<ConceptsSection domain={domain} />
69-
</Suspense>
7068
</Card>
7169

72-
<Card className="p-0 md:col-span-2">
73-
<Suspense fallback={<div>Loading notes...</div>}>
74-
<NotesSection conceptIds={conceptIds} page={page} />
75-
</Suspense>
70+
<Card key={`${conceptIds.join(",")}-${domain}-${page}`} className="p-0 md:col-span-2">
71+
<NotesSection conceptIds={conceptIds} page={page} />
72+
7673
</Card>
7774
</div>
7875
</div>

components/ConceptList.tsx

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Concept } from "@/types/OmopTables";
44
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
55
import { Button } from "@/components/ui/button";
66
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
7+
import { buildSearchParams } from "@/lib/helpers";
78

89
export default function ConceptList({ concepts }: { concepts: Concept[] }) {
910
const router = useRouter();
@@ -12,34 +13,30 @@ export default function ConceptList({ concepts }: { concepts: Concept[] }) {
1213
const selectedConcepts = searchParams.getAll("conceptId").sort();
1314

1415
const onSelect = (conceptId: string) => {
15-
const currentParams = searchParams.getAll("conceptId").sort();
16+
const current = searchParams.getAll("conceptId");
1617

17-
let nextConceptIds: string[];
18+
const nextConceptIds = current.includes(conceptId)
19+
? current.filter((id) => id !== conceptId)
20+
: [...current, conceptId];
1821

19-
if (currentParams.includes(conceptId)) {
20-
nextConceptIds = currentParams.filter((id) => id !== conceptId);
21-
} else {
22-
nextConceptIds = [...currentParams, conceptId];
23-
}
24-
const params = new URLSearchParams();
22+
const query = buildSearchParams({
23+
conceptIds: nextConceptIds,
24+
domain: searchParams.get("domain"),
25+
page: 1,
26+
});
2527

26-
// Keep domain if exists
27-
const domain = searchParams.get("domain");
28-
if (domain && domain !== "All") {
29-
params.set("domain", domain);
30-
}
31-
32-
// Add conceptIds to Url
33-
nextConceptIds.sort();
34-
nextConceptIds.forEach((id) => params.append("conceptId", id));
35-
36-
// Reset page number
37-
params.set("page", "1");
38-
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
28+
router.replace(`${pathname}?${query}`, { scroll: false });
3929
router.refresh();
4030
};
4131
const clearSelection = () => {
42-
router.replace(pathname);
32+
const query = buildSearchParams({
33+
conceptIds: [],
34+
domain: searchParams.get("domain"),
35+
page: 1,
36+
});
37+
38+
router.replace(`${pathname}?${query}`, { scroll: false });
39+
4340
};
4441

4542
return (
@@ -62,7 +59,9 @@ export default function ConceptList({ concepts }: { concepts: Concept[] }) {
6259
: "ghost"
6360
}
6461
className={`w-full justify-between h-auto py-3 px-4 ${
65-
selectedConcepts.includes(c.concept_id) ? "bg-mist-100 dark:bg-neutral-800" : ""
62+
selectedConcepts.includes(c.concept_id)
63+
? "bg-mist-100 dark:bg-neutral-800"
64+
: ""
6665
}`}
6766
onClick={() => onSelect(c.concept_id)}
6867
>

components/NoteCard.tsx

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { ChevronDownIcon } from "@radix-ui/react-icons";
1919
import Link from "next/link";
2020
import { usePathname, useRouter, useSearchParams } from "next/navigation";
2121
import { buildSearchParams } from "@/lib/helpers";
22+
import { useTransition } from "react";
2223

2324
export default function NoteCard({
2425
note,
@@ -31,8 +32,12 @@ export default function NoteCard({
3132
const pathname = usePathname();
3233
const searchParams = useSearchParams();
3334

35+
const [isPending, startTransition] = useTransition();
36+
3437
const selectedConceptIds = searchParams.getAll("conceptId");
38+
3539
const pmcid = note.note_source_value?.match(/PMC(\d+)/)?.[1];
40+
3641
const uniqueConcepts = Array.from(
3742
new Map(
3843
(note.concepts || []).map((c: any) => [
@@ -58,12 +63,21 @@ export default function NoteCard({
5863
page: 1,
5964
});
6065

61-
router.push(`${pathname}?${query}`);
62-
router.refresh();
66+
startTransition(() => {
67+
router.replace(`${pathname}?${query}`, {
68+
scroll: false,
69+
});
70+
});
6371
};
6472

6573
return (
66-
<Card className="bg-zinc-50 ring-foreground/15 dark:bg-neutral-800 mb-5">
74+
<Card
75+
className={`
76+
bg-zinc-50 ring-foreground/15 dark:bg-neutral-800 mb-5
77+
transition-opacity
78+
${isPending ? "opacity-60 pointer-events-none" : ""}
79+
`}
80+
>
6781
<CardHeader>
6882
<CardTitle className="text-lg">
6983
{article?.articleUrl && pmcid ? (
@@ -75,17 +89,29 @@ export default function NoteCard({
7589
{article.title}
7690
</Link>
7791
) : (
78-
<p className="text-sm text-gray-400">Loading article...</p>
92+
<p className="text-sm text-gray-400">
93+
Loading article...
94+
</p>
7995
)}
8096
</CardTitle>
97+
98+
{isPending && (
99+
<div className="text-sm text-muted-foreground">
100+
Loading Case Reports...
101+
</div>
102+
)}
81103
</CardHeader>
82104

83105
<CardContent className="flex flex-wrap items-center gap-2 md:flex-row">
84106
{article ? (
85107
<Collapsible className="w-full rounded-md data-[state=open]:bg-muted">
86108
<CollapsibleTrigger asChild>
87-
<Button variant="ghost" className="group w-full bg-transparent">
109+
<Button
110+
variant="ghost"
111+
className="group w-full bg-transparent"
112+
>
88113
Description
114+
89115
<ChevronDownIcon className="ml-auto transition-transform group-data-[state=open]:rotate-180" />
90116
</Button>
91117
</CollapsibleTrigger>
@@ -95,39 +121,48 @@ export default function NoteCard({
95121
</CollapsibleContent>
96122
</Collapsible>
97123
) : (
98-
<p className="text-sm text-gray-400">Loading article...</p>
124+
<p className="text-sm text-gray-400">
125+
Loading article...
126+
</p>
99127
)}
100128
</CardContent>
101129

102130
<CardFooter className="bg-neutral-50 border-t-gray-200 dark:bg-neutral-900 dark:border-t-neutral-600">
103131
<div className="flex flex-wrap gap-2">
104132
{uniqueConcepts.map((c: Concept) => {
105-
const selected = selectedConceptIds.includes(String(c.concept_id));
133+
const selected = selectedConceptIds.includes(
134+
String(c.concept_id),
135+
);
106136

107137
return (
108138
<Badge
109139
key={c.concept_id}
110-
variant={"secondary"}
140+
variant="secondary"
111141
onClick={() => selectConcept(String(c.concept_id))}
112142
className={`
113143
cursor-pointer transition hover:opacity-80
114144
flex flex-wrap gap-2 py-0 text-sm
145+
115146
${selected ? "ring-2 ring-primary" : ""}
147+
116148
${
117149
c.domain === "Condition"
118150
? "bg-sky-100 dark:bg-[#1B3C53]"
119151
: ""
120152
}
153+
121154
${
122155
c.domain === "Drug"
123156
? "bg-emerald-100 dark:bg-[#3F4F44]"
124157
: ""
125158
}
159+
126160
${
127161
c.domain === "Procedure"
128162
? "bg-violet-100 dark:bg-[#49243E]"
129163
: ""
130164
}
165+
131166
${
132167
c.domain === "Measurement"
133168
? "bg-orange-100 dark:bg-amber-800"
@@ -143,4 +178,4 @@ export default function NoteCard({
143178
</CardFooter>
144179
</Card>
145180
);
146-
}
181+
}

lib/helpers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ export function buildSearchParams({
1616

1717
// Sort and add unique conceptIds
1818
[...new Set(conceptIds)]
19-
.sort((a, b) => Number(a) - Number(b))
19+
.map(String)
20+
.sort()
2021
.forEach((id) => params.append("conceptId", id));
2122

2223
// Set page

0 commit comments

Comments
 (0)