Skip to content

Commit

Permalink
Merge pull request #318 from Pixilib/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
salimkanoun authored Sep 14, 2024
2 parents a6ee04f + ab35f79 commit b8dccb7
Show file tree
Hide file tree
Showing 26 changed files with 685 additions and 255 deletions.
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,15 @@ services:
PYTHON_PLUGIN_ENABLED: "true"
TRANSFERS_PLUGIN_ENABLED: "true"
WORKLISTS_PLUGIN_ENABLED: "true"
ORTHANC__OVERWRITE_INSTANCES: "true"
ORTHANC__DICOM_WEB__ENABLE: "true"
ORTHANC__DICOM_WEB__ROOT: "/dicom-web/"
ORTHANC__DICOM_WEB__ENABLEWADO: "true"
ORTHANC__DICOM_WEB__WADOROOT: "/wado"
ORTHANC__DICOM_WEB__SSL: "true"
ORTHANC__DICOM_WEB__STUDIES_METADATA: "MainDicomTags"
ORTHANC__DICOM_WEB__SERIES_METADATA: "Full"


volumes:
orthanc-flow:
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
"axios": "^1.7.3",
"i18next": "^23.12.2",
"jwt-decode": "^4.0.0",
"mime-types": "^2.1.35",
"moment": "^2.30.1",
"native-file-system-adapter": "^3.0.1",
"preline": "^2.4.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand Down Expand Up @@ -53,6 +55,7 @@
"@storybook/theming": "^8.2.8",
"@tanstack/eslint-plugin-query": "^5.51.15",
"@types/jwt-decode": "^3.1.0",
"@types/mime-types": "^2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-query": "^1.2.9",
Expand Down
1 change: 1 addition & 0 deletions src/content/ContentRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const ContentRoot: React.FC = () => {
return (
<div className="flex flex-col items-center w-full">
<EditPatient
key={editingPatient?.id ?? undefined}
patient={editingPatient as Patient}
onEditPatient={handlePatientUpdate}
onClose={closeEditModal}
Expand Down
13 changes: 11 additions & 2 deletions src/content/patients/AccordionPatient.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, { useState } from "react";

import { Accordion, DeleteButton, EditButton } from "../../ui";
import { Accordion, DeleteButton, DownloadButton, EditButton } from "../../ui";

import Patient from "../../model/Patient";


import StudyRoot from "../studies/StudyRoot";
import SeriesRoot from "../series/SeriesRoot";
import { AccordionHeader } from "../../ui/Accordion";
import { exportRessource } from "../../services/export";
import { useCustomToast } from "../../utils";

type AccordionPatientProps = {
patient: Patient;
Expand All @@ -17,6 +18,7 @@ type AccordionPatientProps = {
};

const AccordionPatient: React.FC<AccordionPatientProps> = ({ patient, onEditPatient, onDeletePatient, onStudyUpdated }) => {
const {toastSuccess} = useCustomToast()
const [selectedStudyId, setSelectedStudyId] = useState<string | null>(null);

const handleStudySelected = (studyId: string) => {
Expand All @@ -33,6 +35,12 @@ const AccordionPatient: React.FC<AccordionPatientProps> = ({ patient, onEditPati
onDeletePatient(patient);
}

const handleSaveClick = (event: React.MouseEvent<HTMLButtonElement|SVGElement>) => {
event.stopPropagation();
toastSuccess("Download started, follow progression in console")
exportRessource("patients", patient.id, (mb)=>{console.log(mb + "mb")})
}

return (
<>
<Accordion
Expand All @@ -44,6 +52,7 @@ const AccordionPatient: React.FC<AccordionPatientProps> = ({ patient, onEditPati
<span className="text-sm group-hover:text-white">Nb of Studies: {patient.getStudies().length}</span>
<div className="flex justify-end w-full space-x-7">
<EditButton onClick={handleEditClick} />
<DownloadButton onClick={handleSaveClick} />
<DeleteButton onClick={handleDeleteClick} />
</div>
</div>
Expand Down
32 changes: 20 additions & 12 deletions src/content/patients/EditPatient.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from "react";
import React, { useState } from "react";
import Patient from "../../model/Patient";
import { modifyPatient } from "../../services";
import { useCustomMutation, useCustomToast } from "../../utils";
import { PatientPayload, OrthancResponse } from "../../utils/types";
import { PatientModifyPayload, OrthancResponse } from "../../utils/types";
import PatientEditForm from './PatientEditForm';
import { Modal } from "../../ui";

Expand All @@ -14,32 +14,40 @@ type EditPatientProps = {
}

const EditPatient: React.FC<EditPatientProps> = ({ patient, onEditPatient, onClose, show }) => {
const { toastSuccess, toastError } = useCustomToast();
const { toastError } = useCustomToast();
const [jobId, setJobId] = useState<string | null>(null);

const { mutateAsync: mutatePatient } = useCustomMutation<OrthancResponse, { id: string, payload: PatientPayload }>(
const { mutateAsync: mutatePatient } = useCustomMutation<OrthancResponse, { id: string, payload: PatientModifyPayload }>(
({ id, payload }) => modifyPatient(id, payload),
[['jobs']],
{
onSuccess: async () => {
toastSuccess(`Patient updated successfully`);
onEditPatient(patient);
onClose();
onSuccess: async (data) => {
setJobId(data.id);
},
onError: (error: any) => {
toastError(`Failed to update patient: ${error}`);
onError: () => {
toastError(`Failed to update patient`);
}
}
);

const handleSubmit = ({ id, payload }: { id: string; payload: PatientPayload }) => {
const handleSubmit = ({ id, payload }: { id: string; payload: PatientModifyPayload }) => {
mutatePatient({ id, payload });
};

const handleJobCompletion = (job: string) => {
if (job === "Success") {
onEditPatient(patient);
onClose();
} else if (job === "Failure") {
toastError(`Failed to update Study `);
}
};

return (
<Modal show={show} size='xl'>
<Modal.Header onClose={onClose}> Edit patient </Modal.Header>
<Modal.Body>
<PatientEditForm patient={patient} onSubmit={handleSubmit} onCancel={onClose} />
<PatientEditForm patient={patient} jobId={jobId} onSubmit={handleSubmit} onJobCompleted={handleJobCompletion} />
</Modal.Body>
</Modal>
);
Expand Down
107 changes: 63 additions & 44 deletions src/content/patients/PatientEditForm.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,62 @@
import React, { ChangeEvent, useState, useEffect } from "react";
import { Button, Input, Spinner } from "../../ui";
import Patient from "../../model/Patient";
import { PatientMainDicomTags, PatientPayload } from "../../utils/types";
import { PatientMainDicomTags, PatientModifyPayload } from "../../utils/types";
import CheckBox from "../../ui/Checkbox";
import { Colors } from "../../utils";
import InputWithDelete from "../../ui/InputWithDelete";
import ProgressJobs from "../../query/ProgressJobs";

type PatientEditFormProps = {
jobId: string | null;
patient: Patient;
onSubmit: (data: { id: string; payload: PatientPayload }) => void;
onCancel: () => void;
onSubmit: (data: { id: string; payload: PatientModifyPayload }) => void;
onJobCompleted: (jobStatus :string) => void;
};

const PatientEditForm = ({ patient, onSubmit, onCancel }: PatientEditFormProps) => {
const [patientId, setPatientId] = useState<string>(patient?.patientId ?? "");
const [patientName, setPatientName] = useState<string | null>(patient?.patientName ?? null);
const [patientBirthDate, setPatientBirthDate] = useState<string | null>(patient?.patientBirthDate ?? null);
const [patientSex, setPatientSex] = useState<string | null>(patient?.patientSex ?? null);
const [removePrivateTags, setRemovePrivateTags] = useState<boolean>(false);
const [keepSource, setKeepSource] = useState<boolean>(false);
const [fieldsToRemove, setFieldsToRemove] = useState<string[]>([]);

if (!patient) return <Spinner/>;

const handleFieldRemoval = (field: string, checked: boolean) => {
setFieldsToRemove((prev) =>
checked ? [...prev, field] : prev.filter((item) => item !== field)
);
};

const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const replace: Partial<PatientMainDicomTags> = {};

if (patientName !== patient.patientName) replace.patientName = patientName;
if (patientBirthDate !== patient.patientBirthDate) replace.patientBirthDate = patientBirthDate;
if (patientSex !== patient.patientSex) replace.patientSex = patientSex;

const payload: PatientPayload = {
replace,
remove: fieldsToRemove,
removePrivateTags,
keepSource,
force: true,
synchronous: false,
};
onSubmit({ id: patientId, payload });
const PatientEditForm = ({ patient, jobId, onSubmit, onJobCompleted }: PatientEditFormProps) => {
const [patientId, setPatientId] = useState<string>(patient?.patientId ?? "");
const [patientName, setPatientName] = useState<string | null>(patient?.patientName ?? null);
const [patientBirthDate, setPatientBirthDate] = useState<string | null>(patient?.patientBirthDate ?? null);
const [patientSex, setPatientSex] = useState<string | null>(patient?.patientSex ?? null);
const [removePrivateTags, setRemovePrivateTags] = useState<boolean>(false);
const [keepSource, setKeepSource] = useState<boolean>(false);
const [fieldsToRemove, setFieldsToRemove] = useState<string[]>([]);
const [keepUIDs, setKeepUIDs] = useState(false)

if (!patient) return <Spinner />;

useEffect(() => {
if (keepUIDs) setKeepSource(true)
}, [keepUIDs])

const handleFieldRemoval = (field: string, checked: boolean) => {
setFieldsToRemove((prev) =>
checked ? [...prev, field] : prev.filter((item) => item !== field)
);
};

const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const replace: Partial<PatientMainDicomTags> = {};

if (patientName !== patient.patientName) replace.patientName = patientName;
if (patientId !== patient.patientId) replace.patientId = patientId;
if (patientBirthDate !== patient.patientBirthDate) replace.patientBirthDate = patientBirthDate;
if (patientSex !== patient.patientSex) replace.patientSex = patientSex;

const payload: PatientModifyPayload = {
replace,
remove: fieldsToRemove,
removePrivateTags,
keepSource,
force: true,
synchronous: false,
keep: keepUIDs ? ['StudyInstanceUID', 'SeriesInstanceUID', 'SOPInstanceUID'] : [],
};

onSubmit({ id: patient.id, payload });
};


return (
<form onSubmit={handleSubmit} className="mt-5 space-y-8">
Expand Down Expand Up @@ -91,9 +100,9 @@ type PatientEditFormProps = {
placeholder="Enter patient sex"
/>
</div>
<div className="grid justify-center grid-cols-1 lg:grid-cols-2">
<div className="flex justify-around">
<CheckBox
label="Removing private tags"
label="Remove private tags"
checked={removePrivateTags}
onChange={(event: ChangeEvent<HTMLInputElement>) => setRemovePrivateTags(event.target.checked)}
bordered={false}
Expand All @@ -104,14 +113,24 @@ type PatientEditFormProps = {
onChange={(event: ChangeEvent<HTMLInputElement>) => setKeepSource(event.target.checked)}
bordered={false}
/>
<CheckBox
label="Keep UIDs"
checked={keepUIDs}
onChange={(e: ChangeEvent<HTMLInputElement>) => setKeepUIDs(e.target.checked)}
bordered={false}
/>
</div>
<div className="flex justify-center mt-4 space-x-4">
<Button color={Colors.secondary} type="button" onClick={onCancel}>
Cancel
</Button>
<div className="flex justify-center">
<Button type="submit" color={Colors.success}>
Save Changes
</Button>
{jobId &&
(
<div className="flex flex-col items-center justify-center">
<ProgressJobs jobId={jobId} onJobCompleted={onJobCompleted} />
</div>
)
}
</div>
</form>
);
Expand Down
7 changes: 4 additions & 3 deletions src/content/series/EditSeries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@

import React, { useState } from "react";
import { modifySeries } from "../../services/orthanc";
import { useCustomMutation, useCustomToast, Series, SeriesPayload } from "../../utils";
import { useCustomMutation, useCustomToast, Series } from "../../utils";
import SeriesEditForm from './SeriesEditForm';
import { Modal } from "../../ui";
import { SeriesModifyPayload } from "../../utils/types";

type EditSeriesProps = {
series: Series;
Expand All @@ -21,7 +22,7 @@ const EditSeries: React.FC<EditSeriesProps> = ({ series, onEditSeries, onClose,
const [jobId, setJobId] = useState<string | null>(null);


const { mutateAsync: mutateSeries } = useCustomMutation<any, { id: string, payload: SeriesPayload }>(
const { mutateAsync: mutateSeries } = useCustomMutation<any, { id: string, payload: SeriesModifyPayload }>(
({ id, payload }) => modifySeries(id, payload),
[['series'], ['jobs']],
{
Expand All @@ -34,7 +35,7 @@ const EditSeries: React.FC<EditSeriesProps> = ({ series, onEditSeries, onClose,
}
);

const handleSubmit = ({ id, payload }: { id: string; payload: SeriesPayload }) => {
const handleSubmit = ({ id, payload }: { id: string; payload: SeriesModifyPayload }) => {
mutateSeries({ id, payload });
};

Expand Down
1 change: 0 additions & 1 deletion src/content/series/PreviewSeries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ const PreviewSeries: React.FC<PreviewSeriesProps> = ({ seriesId}) => {
if (!instanceUIDs) return null
const start = Math.max(imageIndex, 0)
const end = Math.min(start + (pageSize - 1), instanceUIDs.length - 1)
console.log(start, end)
const selectedUIDs = []
for (let i = start; i <= end; i++) {
selectedUIDs.push(instanceUIDs[i])
Expand Down
21 changes: 17 additions & 4 deletions src/content/series/SeriesActions.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SeriesActions.tsx
import React from 'react';
import { FaEdit, FaEye, FaTrash } from "react-icons/fa";
import { RiDownload2Line as DownloadIcon } from "react-icons/ri";
import { Series } from "../../utils/types";
import DropdownButton from '../../ui/menu/DropDownButton';

Expand All @@ -18,17 +19,29 @@ const SeriesActions: React.FC<SeriesActionsProps> = ({ series, onActionClick })
action: () => onActionClick('edit', series)
},
{
label: 'Delete',
icon: <FaTrash />,
color: 'red',
action: () => onActionClick('delete', series)
label: 'Metadata',
icon: <FaEye />,
color: 'green',
action: () => onActionClick('metadata', series),
},
{
label: 'Preview Series',
icon: <FaEye />,
color: 'green',
action: () => onActionClick('preview', series),
},
{
label: 'Download',
icon: <DownloadIcon />,
color: 'green',
action: () => onActionClick('download', series)
},
{
label: 'Delete',
icon: <FaTrash />,
color: 'red',
action: () => onActionClick('delete', series)
},
];

const handleClick = (e: React.MouseEvent) => {
Expand Down
Loading

0 comments on commit b8dccb7

Please sign in to comment.