Skip to content

Commit 8172816

Browse files
authored
Merge pull request #1334 from chhsiao1981/csv-download-button
download queried results as csv
2 parents 46fd521 + 4b1fcb1 commit 8172816

File tree

3 files changed

+136
-6
lines changed

3 files changed

+136
-6
lines changed

src/components/Pacs/PacsView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const PacsView: React.FC<PacsViewProps> = ({
6565
services={services}
6666
service={service}
6767
setService={setService}
68+
studies={studies}
6869
/>
6970
<br />
7071
{studies ? (

src/components/Pacs/components/PacsInput.tsx

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ import type { PACSqueryCore } from "../../../api/pfdcm";
33
import { Select, Input, Row, Col, Grid, Segmented } from "antd";
44
import { useSearchParams } from "react-router-dom";
55
import type { ReadonlyNonEmptyArray } from "fp-ts/ReadonlyNonEmptyArray";
6+
import {
7+
type PacsStudyState,
8+
PacsSeriesCSVKeys,
9+
PacsStudyCSVKeys,
10+
} from "../types";
11+
import { DownloadIcon } from "@patternfly/react-icons";
12+
import OperationButton from "../../NewLibrary/components/operations/OperationButton";
613

714
/** ------------------ Shared Types ------------------ **/
815
type InputFieldProps = {
@@ -12,6 +19,7 @@ type InputFieldProps = {
1219
type PacsInputProps = {
1320
services: ReadonlyNonEmptyArray<string>;
1421
service: string;
22+
studies: PacsStudyState[] | null;
1523
setService: (service: string) => void;
1624
onSubmit: (service: string, query: PACSqueryCore) => void;
1725
};
@@ -115,12 +123,16 @@ const PacsInput: React.FC<PacsInputProps> = ({
115123
service,
116124
setService,
117125
services,
126+
127+
studies: propsStudies,
118128
}) => {
119129
const [searchParams, setSearchParams] = useSearchParams();
120130

121131
// "searchMode" can be "mrn" or "accno"
122132
const searchMode = searchParams.get("searchMode") || "mrn";
123133

134+
const studies = propsStudies || [];
135+
124136
// Helper to update the search mode in the URL
125137
const setSearchMode = React.useCallback(
126138
(mode: string) => {
@@ -185,23 +197,86 @@ const PacsInput: React.FC<PacsInputProps> = ({
185197
</div>
186198
);
187199

200+
const csvLine = (data: string[]) =>
201+
data
202+
.map((v) => v.replaceAll('"', '""')) // escape double quotes
203+
.map((v) => `"${v}"`) // quote it
204+
.join(","); // comma-separated
205+
206+
const downloadStudiesToCSV = () => {
207+
const csvKeys = csvLine(PacsStudyCSVKeys.concat(PacsSeriesCSVKeys));
208+
const studyCSV = studies.map(({ info, series }) =>
209+
series
210+
.map((eachSeries) => {
211+
const studyList: string[] = PacsStudyCSVKeys.map((each) =>
212+
// @ts-expect-error
213+
String(info[each] || ""),
214+
);
215+
const seriesList = PacsSeriesCSVKeys.map((each) =>
216+
// @ts-expect-error
217+
String(eachSeries.info[each] || ""),
218+
);
219+
220+
return csvLine(studyList.concat(seriesList));
221+
})
222+
.join("\r\n"),
223+
);
224+
225+
const theCSV = [csvKeys].concat(studyCSV).join("\r\n");
226+
227+
const blob = new File([theCSV], "PACS.csv", { type: "text/csv" });
228+
229+
const downloadLink = document.createElement("a");
230+
const dataUrl = URL.createObjectURL(blob);
231+
downloadLink.href = dataUrl;
232+
233+
const filenameNumber =
234+
searchMode === "mrn"
235+
? studies[0].info.PatientID
236+
: studies[0].info.AccessionNumber;
237+
238+
const filename = `PACS-${searchMode}-${filenameNumber}.csv`;
239+
downloadLink.download = filename;
240+
241+
document.body.appendChild(downloadLink);
242+
downloadLink.click();
243+
document.body.removeChild(downloadLink);
244+
};
245+
246+
const ariaLabel = "Download queried results as csv";
247+
188248
return (
189-
<Row gutter={2}>
249+
<Row gutter={4}>
190250
<Col xs={24} sm={12} md={8} lg={6} xl={5}>
191251
{searchModeToggle}
192252
</Col>
193253
<Col
194-
xs={{ span: 24, order: 1 }}
195-
sm={{ span: 24, order: 1 }}
196-
md={{ span: 11, order: 0 }}
197-
lg={{ span: 13, order: 0 }}
198-
xl={{ span: 15, order: 0 }}
254+
xs={{ span: 20, order: 1 }}
255+
sm={{ span: 20, order: 1 }}
256+
md={{ span: 6, order: 0 }}
257+
lg={{ span: 9, order: 0 }}
258+
xl={{ span: 11, order: 0 }}
199259
>
200260
{input}
201261
</Col>
202262
<Col xs={24} sm={12} md={5} lg={5} xl={4}>
203263
{serviceDropdown}
204264
</Col>
265+
<Col
266+
xs={{ span: 4, order: 1 }}
267+
sm={{ span: 4, order: 1 }}
268+
md={{ span: 4, order: 0 }}
269+
lg={{ span: 4, order: 0 }}
270+
xl={{ span: 4, order: 0 }}
271+
>
272+
<OperationButton
273+
handleOperations={downloadStudiesToCSV}
274+
count={studies?.length}
275+
icon={<DownloadIcon />}
276+
ariaLabel={ariaLabel}
277+
operationKey="download"
278+
/>
279+
</Col>
205280
</Row>
206281
);
207282
};

src/components/Pacs/types.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,60 @@ type PacsStudyState = {
136136
series: PacsSeriesState[];
137137
};
138138

139+
export type PacsSeriesCSV = {
140+
SpecificCharacterSet: string;
141+
StudyDate: Date | null;
142+
AccessionNumber: string;
143+
RetrieveAETitle: string;
144+
ModalitiesInStudy: string;
145+
StudyDescription: string;
146+
PatientName: string;
147+
PatientID: string;
148+
PatientBirthDate: Date | null;
149+
PatientSex: string;
150+
PatientAge: string;
151+
ProtocolName: string;
152+
AcquisitionProtocolName: string;
153+
AcquisitionProtocolDescription: string;
154+
StudyInstanceUID: string;
155+
NumberOfStudyRelatedSeries: number;
156+
PerformedStationAETitle: string;
157+
158+
SeriesDate: Date | null;
159+
Modality: string;
160+
SeriesDescription: string;
161+
SeriesInstanceUID: string;
162+
NumberOfSeriesRelatedInstances: number | null;
163+
};
164+
165+
export const PacsStudyCSVKeys = [
166+
"SpecificCharacterSet",
167+
"StudyDate",
168+
"AccessionNumber",
169+
"RetrieveAETitle",
170+
"ModalitiesInStudy",
171+
"StudyDescription",
172+
"PatientName",
173+
"PatientID",
174+
"PatientBirthDate",
175+
"PatientSex",
176+
"PatientAge",
177+
"ProtocolName",
178+
"AcquisitionProtocolName",
179+
"AcquisitionProtocolDescription",
180+
"StudyInstanceUID",
181+
"NumberOfStudyRelatedSeries",
182+
"PerformedStationAETitle",
183+
];
184+
185+
export const PacsSeriesCSVKeys = [
186+
"SeriesDate",
187+
"Modality",
188+
"SeriesDescription",
189+
"SeriesInstanceUID",
190+
"NumberOfSeriesRelatedInstances",
191+
];
192+
139193
/**
140194
* PACS user interface preferences.
141195
*/

0 commit comments

Comments
 (0)