Skip to content

Commit 7cc6aee

Browse files
committed
Add support for marking table as DEATH table on the client side
1 parent e4964d6 commit 7cc6aee

10 files changed

Lines changed: 250 additions & 24 deletions

File tree

app/next-client-app/api/scanreports.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ export async function getScanReportTable(
149149
permissions: [],
150150
jobs: [],
151151
trigger_reuse: true,
152+
death_table: false,
152153
};
153154
}
154155
}

app/next-client-app/app/(protected)/scanreports/[id]/columns.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { ColumnDef } from "@tanstack/react-table";
44
import { DataTableColumnHeader } from "@/components/data-table/DataTableColumnHeader";
55
import { EditButton } from "@/components/scanreports/EditButton";
6+
import { Tooltips } from "@/components/core/Tooltips";
67
import JobDialog from "@/components/jobs/JobDialog";
78
import { FindGeneralStatus, DivideJobs } from "@/components/jobs/JobUtils";
89
import Link from "next/link";
@@ -89,6 +90,28 @@ export const columns: ColumnDef<ScanReportTable>[] = [
8990
);
9091
}
9192
},
93+
{
94+
id: "note",
95+
header: ({ column }) => (
96+
<DataTableColumnHeader column={column} title="Note" />
97+
),
98+
cell: ({ row }) => {
99+
const { death_table } = row.original;
100+
return (
101+
<div>
102+
{death_table && (
103+
<h3 className="flex">
104+
{" "}
105+
Death table
106+
<Tooltips
107+
content="This table is marked as a Death table. In the Carrot data standard, death data is typically provided in a separate file (e.g. death.csv) for mapping to the OMOP Death table."
108+
/>
109+
</h3>
110+
)}
111+
</div>
112+
);
113+
}
114+
},
92115
{
93116
id: "edit",
94117
header: ({ column }) => <DataTableColumnHeader column={column} title="" />,

app/next-client-app/app/(protected)/scanreports/[id]/tables/[tableId]/fields/[fieldId]/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ export default async function ScanReportsValue(props: ScanReportsValueProps) {
7272
id={id}
7373
tableId={tableId}
7474
fieldId={fieldId}
75-
tableName={table.name}
75+
tableName={
76+
table.death_table ? `${table.name} (Death table)` : table.name
77+
}
7678
fieldName={field.name}
7779
variant="field"
7880
/>

app/next-client-app/app/(protected)/scanreports/[id]/tables/[tableId]/fields/[fieldId]/update/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ export default async function ScanReportsEditField(props: ScanReportsEditFieldPr
4040
id={id}
4141
tableId={tableId}
4242
fieldId={fieldId}
43-
tableName={table.name}
43+
tableName={
44+
table.death_table ? `${table.name} (Death table)` : table.name
45+
}
4446
fieldName={field.name}
4547
variant="fieldUpdate"
4648
/>

app/next-client-app/app/(protected)/scanreports/[id]/tables/[tableId]/page.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,11 @@ export default async function ScanReportsField(props: ScanReportsFieldProps) {
6060
<TableBreadcrumbs
6161
id={id}
6262
tableId={tableId}
63-
tableName={tableName.name}
63+
tableName={
64+
tableName.death_table
65+
? `${tableName.name} (Death table)`
66+
: tableName.name
67+
}
6468
variant="table"
6569
/>
6670
<div>

app/next-client-app/app/(protected)/scanreports/[id]/tables/[tableId]/update/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export default async function UpdateTable(props: UpdateTableProps) {
7474
<div>
7575
<Link href={`/scanreports/${id}/tables/${tableId}`}>
7676
<Button variant={"secondary"} className="mb-3">
77-
Update Table: {table.name}
77+
Update Table: {table.name} {table.death_table && "(Death table)"}
7878
</Button>
7979
</Link>
8080
{(table.date_event === null || table.person_id === null) && (

app/next-client-app/components/core/Tooltips.tsx

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import {
66
} from "@/components/ui/tooltip";
77

88
import { InfoIcon } from "lucide-react";
9+
import { ReactElement } from "react";
910

1011
export function Tooltips({
1112
content,
1213
link,
1314
side = "top",
1415
}: {
15-
content: string;
16+
content: string | ReactElement;
1617
link?: string;
1718
side?: "top" | "bottom" | "left" | "right";
1819
}) {
@@ -26,24 +27,28 @@ export function Tooltips({
2627
className="max-w-96 text-center whitespace-pre-wrap"
2728
side={side}
2829
>
29-
<p>
30-
{content}
31-
{link && (
32-
<>
33-
{" "}
34-
Find out more{" "}
35-
<a
36-
href={link}
37-
className="underline"
38-
target="_blank"
39-
rel="noopener noreferrer"
40-
>
41-
here
42-
</a>
43-
.
44-
</>
45-
)}
46-
</p>
30+
{typeof content === "string" ? (
31+
<p>
32+
{content}
33+
{link && (
34+
<>
35+
{" "}
36+
Find out more{" "}
37+
<a
38+
href={link}
39+
className="underline"
40+
target="_blank"
41+
rel="noopener noreferrer"
42+
>
43+
here
44+
</a>
45+
.
46+
</>
47+
)}
48+
</p>
49+
) : (
50+
content
51+
)}
4752
</TooltipContent>
4853
</Tooltip>
4954
</TooltipProvider>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
"use client";
2+
3+
import {
4+
Dialog,
5+
DialogClose,
6+
DialogContent,
7+
DialogDescription,
8+
DialogFooter,
9+
DialogHeader,
10+
DialogTitle,
11+
} from "../ui/dialog";
12+
import { toast } from "sonner";
13+
import { Button } from "../ui/button";
14+
import { updateScanReportTable } from "@/api/scanreports";
15+
import { useRouter } from "next/navigation";
16+
17+
interface DeathTableConfirmDialogProps {
18+
open: boolean;
19+
onOpenChange: (open: boolean) => void;
20+
scanReportId: number;
21+
tableId: number;
22+
tableName: string;
23+
isDeathTable: boolean;
24+
onSuccess?: () => void;
25+
}
26+
27+
export function DeathTableConfirmDialog({
28+
open,
29+
onOpenChange,
30+
scanReportId,
31+
tableId,
32+
tableName,
33+
isDeathTable,
34+
onSuccess,
35+
}: DeathTableConfirmDialogProps) {
36+
const router = useRouter();
37+
const markingAsDeath = !isDeathTable;
38+
39+
const handleConfirm = async () => {
40+
const response = await updateScanReportTable(
41+
scanReportId,
42+
tableId,
43+
{ death_table: markingAsDeath },
44+
);
45+
if (response) {
46+
toast.error(
47+
`Failed to update DEATH table setting: ${response.errorMessage}`,
48+
);
49+
} else {
50+
toast.success(
51+
markingAsDeath
52+
? "Table marked as DEATH table successfully."
53+
: "Table unmarked as DEATH table successfully.",
54+
);
55+
onOpenChange(false);
56+
onSuccess?.();
57+
router.refresh();
58+
}
59+
};
60+
61+
return (
62+
<Dialog open={open} onOpenChange={onOpenChange}>
63+
<DialogContent>
64+
<DialogHeader className="text-start">
65+
<DialogTitle>
66+
{markingAsDeath ? "Mark as DEATH table" : "Unmark as DEATH table"}
67+
</DialogTitle>
68+
<DialogDescription>
69+
{markingAsDeath ? (
70+
<>
71+
Are you sure you want to mark &quot;{tableName}&quot; as a
72+
DEATH table? This will enable auto-mapping as a death table for
73+
any death SR uploads.
74+
</>
75+
) : (
76+
<>
77+
Are you sure you want to unmark &quot;{tableName}&quot; as a
78+
DEATH table? Auto-mapping as a death table will no longer apply
79+
to this table.
80+
</>
81+
)}
82+
</DialogDescription>
83+
</DialogHeader>
84+
<DialogFooter className="flex-col space-y-2 sm:space-y-0 sm:space-x-2">
85+
<Button onClick={handleConfirm}>
86+
{markingAsDeath ? "Mark as DEATH table" : "Unmark"}
87+
</Button>
88+
<DialogClose asChild>
89+
<Button variant="outline">Cancel</Button>
90+
</DialogClose>
91+
</DialogFooter>
92+
</DialogContent>
93+
</Dialog>
94+
);
95+
}

app/next-client-app/components/scanreports/ScanReportTableUpdateForm.tsx

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
11
"use client";
22

3+
import { useState } from "react";
34
import { Button } from "@/components/ui/button";
45
import { updateScanReportTable } from "@/api/scanreports";
5-
import { Save } from "lucide-react";
6+
import { Check, Save, X } from "lucide-react";
67
import { toast } from "sonner";
78
import { FormDataFilter } from "../form-components/FormikUtils";
89
import { Formik } from "formik";
910
import { FormField, FormItem, FormLabel, FormControl, FormMessage, FormDescription } from "@/components/ui/form";
1011
import { FormikSelect } from "../form-components/FormikSelect";
1112
import { useRouter } from "next/navigation";
1213
import { Checkbox } from "../ui/checkbox";
14+
import { Switch } from "@/components/ui/switch";
15+
import { Label } from "@/components/ui/label";
16+
import {
17+
Dialog,
18+
DialogContent,
19+
DialogDescription,
20+
DialogFooter,
21+
DialogHeader,
22+
DialogTitle,
23+
} from "@/components/ui/dialog";
24+
import { Tooltips } from "@/components/core/Tooltips";
1325
import { enableReuseTriggerOption } from "@/constants";
1426

1527
interface FormData {
1628
personId: number | null;
1729
dateEvent: number | null;
1830
triggerReuse: boolean;
31+
death_table: boolean;
1932
}
2033

2134
export function ScanReportTableUpdateForm({
@@ -32,6 +45,7 @@ export function ScanReportTableUpdateForm({
3245
dateEvent: ScanReportField;
3346
}) {
3447
const router = useRouter();
48+
const [isDialogOpen, setIsDialogOpen] = useState(false);
3549
const canUpdate =
3650
permissions.includes("CanEdit") || permissions.includes("CanAdmin");
3751

@@ -45,6 +59,7 @@ export function ScanReportTableUpdateForm({
4559
person_id: data.personId !== 0 ? data.personId : null,
4660
date_event: data.dateEvent !== 0 ? data.dateEvent : null,
4761
trigger_reuse: data.triggerReuse,
62+
death_table: data.death_table,
4863
};
4964

5065
const response = await updateScanReportTable(
@@ -68,6 +83,7 @@ export function ScanReportTableUpdateForm({
6883
dateEvent: initialDateEvent[0].value,
6984
personId: initialPersonId[0].value,
7085
triggerReuse: scanreportTable.trigger_reuse,
86+
death_table: Boolean(scanreportTable.death_table),
7187
}}
7288
onSubmit={(data) => {
7389
handleSubmit(data);
@@ -109,6 +125,83 @@ export function ScanReportTableUpdateForm({
109125
</FormControl>
110126
</FormItem>
111127

128+
<FormItem>
129+
<div className="flex flex-wrap items-center gap-2">
130+
<FormLabel className="mb-0">
131+
Does this table contain only death data for the OMOP CDM Death
132+
table?
133+
</FormLabel>
134+
<Tooltips
135+
content="In the Carrot data standard, death data is provided in a separate file (e.g. death.csv). Mark Yes if this table contains that death data to be mapped to the OMOP Death table."
136+
/>
137+
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
138+
<DialogContent>
139+
<DialogHeader>
140+
<DialogTitle>Please Confirm Your Choice</DialogTitle>
141+
<DialogDescription>
142+
Are you sure you want to set this table as a Death table?
143+
Doing so will result in the following:
144+
<ul className="text-gray-500 list-disc pl-4 py-2">
145+
<li>
146+
Mapping Rules that are created either manually or
147+
automatically (built from OMOP vocabulary or Reused)
148+
will have Destination table as{" "}
149+
<span className="font-bold">Death</span>.
150+
</li>
151+
<li>
152+
All concepts in this table will be recognised as{" "}
153+
<span className="font-bold">Cause of Death</span> in
154+
OMOP CDM.
155+
</li>
156+
<li>
157+
Destination of Date Event will be{" "}
158+
<span className="font-bold">Death date</span> field in
159+
OMOP CDM.
160+
</li>
161+
</ul>
162+
<p className="text-muted-foreground text-pretty">
163+
You can turn off this setting later. Mapping rules will
164+
be refreshed when you save.
165+
</p>
166+
</DialogDescription>
167+
</DialogHeader>
168+
<DialogFooter className="flex gap-3">
169+
<Button
170+
type="button"
171+
onClick={() => setIsDialogOpen(false)}
172+
variant="outline"
173+
>
174+
Cancel <X className="size-4 ml-2" />
175+
</Button>
176+
<Button
177+
type="button"
178+
onClick={() => {
179+
setFieldValue("death_table", true);
180+
setIsDialogOpen(false);
181+
}}
182+
>
183+
Confirm <Check className="size-4 ml-2" />
184+
</Button>
185+
</DialogFooter>
186+
</DialogContent>
187+
</Dialog>
188+
<Switch
189+
checked={values.death_table}
190+
onCheckedChange={(checked) => {
191+
if (checked) {
192+
setIsDialogOpen(true);
193+
} else {
194+
setFieldValue("death_table", false);
195+
}
196+
}}
197+
disabled={!canUpdate}
198+
/>
199+
<Label className="text-lg">
200+
{values.death_table === true ? "YES" : "NO"}
201+
</Label>
202+
</div>
203+
</FormItem>
204+
112205
{enableReuseTriggerOption === "true" && (
113206
<FormField name="triggerReuse">
114207
{({ field }) => (

app/next-client-app/types/scanreport.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ interface ScanReportTable {
2929
permissions: Permission[];
3030
jobs: Job[];
3131
trigger_reuse: boolean;
32+
death_table?: boolean;
3233
}
3334

3435
interface ScanReportField {

0 commit comments

Comments
 (0)