-
Results
-
+
+
Results
+
+
+
+
+
+
+
+
+
{isPending ? (
) : (
@@ -118,14 +193,18 @@ const ContentRoot: React.FC = () => {
setEditingPatient(patient)}
onStudyUpdated={() => refreshFind()}
+ onSelectedStudyChange={(selectedState) => setSelectedStudies(selectedState)}
+ selectedStudies={selectedStudies}
/>
))
)}
+
);
};
diff --git a/src/content/patients/AccordionPatient.tsx b/src/content/patients/AccordionPatient.tsx
index 3d71babb..f46c7df0 100644
--- a/src/content/patients/AccordionPatient.tsx
+++ b/src/content/patients/AccordionPatient.tsx
@@ -1,6 +1,6 @@
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
-import { Accordion, DeleteButton, DownloadButton, EditButton } from "../../ui";
+import { Accordion, CheckBox, DeleteButton, DownloadButton, EditButton } from "../../ui";
import Patient from "../../model/Patient";
@@ -12,15 +12,25 @@ import { useCustomToast } from "../../utils";
type AccordionPatientProps = {
patient: Patient;
+ onPatientSelectionChange: (selected: boolean, patient: Patient) => void
onEditPatient: (patient: Patient) => void;
onStudyUpdated: (patient: Patient) => void;
onDeletePatient: (patient: Patient) => void;
+ selectedStudies: { [studyId: string]: boolean }
+ onSelectedStudyChange: (selectedState: { [studyId: string]: boolean }) => void
};
-const AccordionPatient: React.FC
= ({ patient, onEditPatient, onDeletePatient, onStudyUpdated }) => {
+const AccordionPatient = ({ patient, onPatientSelectionChange, onEditPatient, onDeletePatient, onStudyUpdated, selectedStudies, onSelectedStudyChange }: AccordionPatientProps) => {
+
const { toastSuccess, updateExistingToast } = useCustomToast()
+ const [selected, setSelected] = useState(false)
const [selectedStudyId, setSelectedStudyId] = useState(null);
+
+ useEffect(() => {
+ onPatientSelectionChange(selected, patient)
+ }, [selected])
+
const handleStudySelected = (studyId: string) => {
setSelectedStudyId(studyId);
};
@@ -51,6 +61,7 @@ const AccordionPatient: React.FC = ({ patient, onEditPati
Name: {patient.patientName}
Nb of Studies: {patient.getStudies().length}
+
event.stopPropagation()} onChange={(event) => setSelected(event.target.checked)} checked={selected} />
@@ -61,12 +72,14 @@ const AccordionPatient: React.FC = ({ patient, onEditPati
}
className="w-full rounded-2xl"
>
-
+
onStudyUpdated(patient)}
onStudySelected={handleStudySelected}
+ selectedStudies={selectedStudies}
+ onSelectedStudyChange={onSelectedStudyChange}
/>
{selectedStudyId && (
diff --git a/src/content/studies/StudyRoot.tsx b/src/content/studies/StudyRoot.tsx
index 36c67bab..c6fd6899 100644
--- a/src/content/studies/StudyRoot.tsx
+++ b/src/content/studies/StudyRoot.tsx
@@ -1,43 +1,31 @@
import React, { useMemo, useState } from 'react';
-import { useDispatch } from 'react-redux';
import { useCustomMutation } from '../../utils/reactQuery';
import { useCustomToast } from '../../utils/toastify';
-import { Colors } from '../../utils';
-import { addStudyIdToDeleteList, addSeriesToExportList, addStudyIdToAnonymizeList } from '../../utils/actionsUtils';
import { deleteStudy } from '../../services/orthanc';
import { exportRessource } from '../../services/export';
import { useConfirm } from '../../services/ConfirmContextProvider';
import Patient from '../../model/Patient';
-
import StudyTable from './StudyTable';
import EditStudy from './EditStudy';
import PreviewStudy from './PreviewStudy';
import AiStudy from './AiStudy';
-import Toolsbar from '../../ui/Toolsbar';
-import { Button } from '../../ui';
-import { addStudyToAnonymizeList } from '../../reducers/AnonymizeSlice';
-
-import AnonIcon from '../../ui/AnonIcon';
-import { BsTrashFill as DeleteIcon } from "react-icons/bs";
-import { FaFileExport as ExportIcon } from "react-icons/fa";
type StudyRootProps = {
patient: Patient;
onStudyUpdated: () => void;
onStudySelected?: (studyId: string) => void;
+ selectedStudies: { [studyId: string]: boolean }
+ onSelectedStudyChange: (selectedState: { [studyId: string]: boolean }) => void
};
-const StudyRoot: React.FC
= ({ patient, onStudyUpdated, onStudySelected }) => {
- const dispatch = useDispatch();
+const StudyRoot = ({ patient, onStudyUpdated, onStudySelected, selectedStudies, onSelectedStudyChange } :StudyRootProps) => {
const [editingStudy, setEditingStudy] = useState(null);
const [aiStudyId, setAIStudyId] = useState(null);
const [previewStudyId, setPreviewStudyId] = useState(null);
- const [selectedStudies, setSelectedStudies] = useState<{ [studyId: string]: boolean }>({});
- const [isToolsbarVisible, setIsToolsbarVisible] = useState(true);
const { confirm } = useConfirm();
const { toastSuccess, toastError, updateExistingToast } = useCustomToast();
@@ -89,37 +77,6 @@ const StudyRoot: React.FC = ({ patient, onStudyUpdated, onStudyS
exportRessource("studies", studyId, (mb) => updateExistingToast(id, `Downloaded ${mb} mb`));
};
- const handleRowSelectionChange = (selectedState: { [studyId: string]: boolean }) => {
- setSelectedStudies(selectedState);
- };
-
- const handleSendDeleteList = async () => {
- const studyIds = Object.keys(selectedStudies);
- for (const studyId of studyIds) {
- await addStudyIdToDeleteList(studyId);
- }
- };
-
- const handleSendExportList = () => {
- const studyIds = Object.keys(selectedStudies);
- studyIds.forEach(studyId => {
- const series = studies.find(study => study.id === studyId);
- if (series) {
- dispatch(addSeriesToExportList({ series }));
- }
- });
- };
-
- const handleSendAnonymizeList = () => {
- const studyIds = Object.keys(selectedStudies);
- studyIds.forEach(studyId => {
- const series = studies.find(study => study.id === studyId);
- if (series) {
- dispatch(addStudyToAnonymizeList({ series }));
- }
- });
- };
-
const handleStudyAction = (action: string, studyId: string) => {
switch (action) {
case 'edit':
@@ -155,7 +112,7 @@ const StudyRoot: React.FC = ({ patient, onStudyUpdated, onStudyS
onRowClick={handleRowClick}
onActionClick={handleStudyAction}
selectedRows={selectedStudies}
- onRowSelectionChange={handleRowSelectionChange}
+ onRowSelectionChange={onSelectedStudyChange}
/>
{editingStudy && (
= ({ patient, onStudyUpdated, onStudyS
/>
)}
-
- {isToolsbarVisible && (
-
//
-
-
-
-
-
-
- )}
);
};
diff --git a/src/delete/DeleteQueues.tsx b/src/delete/DeleteQueues.tsx
new file mode 100644
index 00000000..b6e9ebb0
--- /dev/null
+++ b/src/delete/DeleteQueues.tsx
@@ -0,0 +1,29 @@
+import { useSelector } from "react-redux"
+import { getExistingDeleteQueues } from "../services/queues"
+import { useCustomQuery } from "../utils"
+import { RootState } from "../store"
+import { Spinner } from "../ui"
+import ProgressQueue from "./ProgressQueue"
+
+
+const DeleteQueues = () => {
+
+ const currentUserId = useSelector((state: RootState) => state.user.currentUserId);
+
+ const {data : existingDeleteQueues, isPending } = useCustomQuery(['queue', 'delete', currentUserId.toString()], () => getExistingDeleteQueues(currentUserId))
+
+ if(isPending) return
+
+ return (
+
+
Progress Queue
+ {
+ existingDeleteQueues?.map((uuid)=>{
+ return
+ })
+ }
+
+ )
+}
+
+export default DeleteQueues
\ No newline at end of file
diff --git a/src/delete/DeleteRoot.tsx b/src/delete/DeleteRoot.tsx
new file mode 100644
index 00000000..08c7249a
--- /dev/null
+++ b/src/delete/DeleteRoot.tsx
@@ -0,0 +1,67 @@
+import { useSelector, useDispatch } from "react-redux";
+import { RootState } from "../store";
+import DeleteStudyTable from "./DeleteStudyTable";
+import { flushDeleteList } from "../reducers/DeleteSlice";
+import { Button, Card, CardHeader, CardBody, CardFooter } from "../ui";
+import { Colors, useCustomMutation } from "../utils";
+import { createDeleteQueue } from "../services/queues";
+import { useState } from "react";
+import ProgressQueue from "./ProgressQueue";
+import DeleteQueues from "./DeleteQueues";
+
+const DeleteRoot = () => {
+ const dispatch = useDispatch();
+ const deleteList = useSelector((state: RootState) => state.delete.studies);
+
+ const [queueUuid, setQueueUuid] = useState(null);
+
+ const handleClearList = () => {
+ dispatch(flushDeleteList());
+ };
+
+ const { mutate: mutateDelete } = useCustomMutation(
+ ({ seriesIds }) => createDeleteQueue(seriesIds),
+ [[]],
+ {
+ onSuccess: (uuid) => {
+ setQueueUuid(uuid)
+ },
+ }
+ )
+
+ const handleDeleteList = () => {
+ const seriesIds = Object.values(deleteList).map((study) => study.series).flat();
+ mutateDelete({ seriesIds });
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+
+export default DeleteRoot;
diff --git a/src/delete/DeleteStudyTable.tsx b/src/delete/DeleteStudyTable.tsx
new file mode 100644
index 00000000..4ca869d5
--- /dev/null
+++ b/src/delete/DeleteStudyTable.tsx
@@ -0,0 +1,67 @@
+import { useMemo } from "react";
+import { useDispatch } from "react-redux";
+import { ColumnDef } from "@tanstack/react-table";
+
+import { Table, Button } from "../ui";
+import { Colors, Study } from "../utils";
+import { removeStudyFromDeleteList } from "../reducers/DeleteSlice";
+
+type DeleteStudyTableProps = {
+ studies: Study[];
+};
+
+const DeleteStudyTable = ({ studies }: DeleteStudyTableProps) => {
+ const dispatch = useDispatch();
+
+ const handleDelete = (studyId: string) => {
+ dispatch(removeStudyFromDeleteList({ studyId }));
+ };
+
+ const columns: ColumnDef[] = useMemo(
+ () => [
+ {
+ id: "id",
+ accessorKey: "id",
+ header: "ID",
+ },
+ {
+ accessorKey: "patientMainDicomTags.patientName",
+ header: "Patient Name",
+ },
+ {
+ accessorKey: "patientMainDicomTags.patientId",
+ header: "Patient ID",
+ },
+ {
+ accessorKey: "mainDicomTags.accessionNumber",
+ header: "Accession Number",
+ },
+ {
+ accessorKey: "mainDicomTags.studyDate",
+ header: "Acquisition Date",
+ },
+ {
+ accessorKey: "mainDicomTags.studyDescription",
+ header: "Study Description",
+ },
+ {
+ header: "Actions",
+ cell: ({ row }) => (
+
+
+
+ ),
+ },
+ ],
+ [dispatch]
+ );
+
+ return ;
+};
+
+export default DeleteStudyTable;
diff --git a/src/delete/ProgressQueue.tsx b/src/delete/ProgressQueue.tsx
new file mode 100644
index 00000000..4f9df9e1
--- /dev/null
+++ b/src/delete/ProgressQueue.tsx
@@ -0,0 +1,25 @@
+import { deleteDeleteQueue, getDeleteQueue } from "../services/queues"
+import { Colors, useCustomMutation, useCustomQuery } from "../utils"
+import { Button, ProgressCircle, Spinner } from "../ui"
+
+type ProgressQueueProps = {
+ uuid: string
+}
+const ProgressQueue = ({ uuid }: ProgressQueueProps) => {
+ const { data, isPending } = useCustomQuery(['queue', 'delete', uuid], () => getDeleteQueue(uuid), {refetchInterval : 2000})
+ const {mutate : mutateDeleteQueue} = useCustomMutation(
+ ()=> deleteDeleteQueue(uuid),
+ [['queue', 'delete']]
+ )
+
+ if (isPending) return
+
+ return (
+
+
+
+
+ )
+}
+
+export default ProgressQueue
\ No newline at end of file
diff --git a/src/export/ExportRoot.tsx b/src/export/ExportRoot.tsx
new file mode 100644
index 00000000..ec4bcf84
--- /dev/null
+++ b/src/export/ExportRoot.tsx
@@ -0,0 +1,48 @@
+import { useDispatch, useSelector } from "react-redux";
+import { RootState } from "../store";
+import ExportStudyTable from "./ExportStudyTable";
+import ExportSeriesTable from "./ExportSeriesTable";
+import { Button, Card, CardFooter } from "../ui";
+import { Colors } from "../utils";
+
+const ExportRoot = () => {
+
+ const dispatch = useDispatch();
+ const exportSeriesList = useSelector((state: RootState) => state.export.series);
+ const exportStudyList = useSelector((state: RootState) => state.export.studies);
+
+ const handleClearList = () => {
+
+ }
+
+ const handleExport = () => {
+
+ }
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default ExportRoot
\ No newline at end of file
diff --git a/src/export/ExportSeriesTable.tsx b/src/export/ExportSeriesTable.tsx
new file mode 100644
index 00000000..396366ca
--- /dev/null
+++ b/src/export/ExportSeriesTable.tsx
@@ -0,0 +1,60 @@
+import { ColumnDef } from "@tanstack/react-table";
+import { Button, Table } from "../ui"
+import { Colors, Series } from "../utils";
+import { useMemo } from "react";
+
+type ExportSeriesTableProps = {
+ series: Series[]
+}
+const ExportSeriesTable = ({ series }: ExportSeriesTableProps) => {
+ console.log(series)
+
+ const handleDelete = (seriesId: string) => {
+ };
+
+ const columns: ColumnDef[] = useMemo(
+ () => [
+ {
+ id: "id",
+ accessorKey: "id",
+ header: "ID",
+ },
+ {
+ accessorKey: "mainDicomTags.seriesDescription",
+ header: "Series Description",
+ },
+ {
+ accessorKey: "mainDicomTags.modality",
+ header: "Modality",
+ },
+ {
+ accessorKey: "mainDicomTags.seriesNumber",
+ header: "Series Number",
+ },
+ {
+ accessorFn: (row) => row.instances.length,
+ header: "Instances",
+ },
+ {
+ header: "Actions",
+ cell: ({ row }) => (
+
+
+
+ ),
+ },
+ ],
+ []
+ );
+
+ return (
+
+ )
+}
+
+export default ExportSeriesTable
\ No newline at end of file
diff --git a/src/export/ExportStudyTable.tsx b/src/export/ExportStudyTable.tsx
new file mode 100644
index 00000000..69b43d2a
--- /dev/null
+++ b/src/export/ExportStudyTable.tsx
@@ -0,0 +1,65 @@
+import { useMemo } from "react";
+import { useDispatch } from "react-redux";
+import { ColumnDef } from "@tanstack/react-table";
+
+import { Table, Button } from "../ui";
+import { Colors, Study } from "../utils";
+
+type ExportStudyTableProps = {
+ studies: Study[];
+};
+
+const ExportStudyTable = ({ studies }: ExportStudyTableProps) => {
+ const dispatch = useDispatch();
+
+ const handleDelete = (studyId: string) => {
+ };
+
+ const columns: ColumnDef[] = useMemo(
+ () => [
+ {
+ id: "id",
+ accessorKey: "id",
+ header: "ID",
+ },
+ {
+ accessorKey: "patientMainDicomTags.patientName",
+ header: "Patient Name",
+ },
+ {
+ accessorKey: "patientMainDicomTags.patientId",
+ header: "Patient ID",
+ },
+ {
+ accessorKey: "mainDicomTags.accessionNumber",
+ header: "Accession Number",
+ },
+ {
+ accessorKey: "mainDicomTags.studyDate",
+ header: "Acquisition Date",
+ },
+ {
+ accessorKey: "mainDicomTags.studyDescription",
+ header: "Study Description",
+ },
+ {
+ header: "Actions",
+ cell: ({ row }) => (
+
+
+
+ ),
+ },
+ ],
+ [dispatch]
+ );
+
+ return ;
+};
+
+export default ExportStudyTable;
diff --git a/src/reducers/DeleteSlice.ts b/src/reducers/DeleteSlice.ts
index f484d2f7..3ad1f6c0 100644
--- a/src/reducers/DeleteSlice.ts
+++ b/src/reducers/DeleteSlice.ts
@@ -3,9 +3,7 @@ import { Study } from '../utils/types';
export interface DeleteState {
studies: {
- [studyId: string]: {
- study: Study,
- }
+ [studyId: string]: Study,
}
}
@@ -28,9 +26,7 @@ const deleteSlice = createSlice({
addStudyToDeleteList: (state, action: PayloadAction) => {
const study = action.payload.study;
- state.studies[study.id] = {
- study: study,
- }
+ state.studies[study.id] = study
},
flushDeleteList : (state) =>{
diff --git a/src/reducers/ExportSlice.ts b/src/reducers/ExportSlice.ts
index 21ed4a26..aad21b8b 100644
--- a/src/reducers/ExportSlice.ts
+++ b/src/reducers/ExportSlice.ts
@@ -1,15 +1,17 @@
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
-import { Series } from '../utils/types';
+import { Series, Study } from '../utils/types';
export interface ExportState {
+ studies: {
+ [studyId: string]: Study
+ }
series: {
- [seriesId: string]: {
- series: Series,
- }
+ [seriesId: string]: Series
}
}
type AddExportPayload = {
+ study: Study
series: Series
}
@@ -18,6 +20,7 @@ type RemoveExportPayload = {
}
const initialState: ExportState = {
+ studies: {},
series: {},
}
@@ -27,15 +30,21 @@ const exportSlice = createSlice({
reducers: {
addSeriesToExportList: (state, action: PayloadAction) => {
const series = action.payload.series;
- state.series[series.id] = {
- series: series,
- }
+ const study = action.payload.study;
+ state.series[series.id] = series
+ state.studies[study.id] = study
},
flushExportList: (state) => {
state.series = {}
+ state.studies = {}
},
removeSeriesFromExportList: (state, action: PayloadAction) => {
const seriesId = action.payload.seriesId;
+ const seriesToDelete = state.series[seriesId]
+ //If delete series is the last series in the study, delete the study
+ if (Object.values(state.series).filter(series => series.parentStudy === seriesToDelete.parentStudy).length === 1) {
+ delete state.studies?.[seriesToDelete.parentStudy]
+ }
delete state.series?.[seriesId]
}
}
diff --git a/src/root/DeleteList.tsx b/src/root/DeleteList.tsx
deleted file mode 100644
index 991ed03c..00000000
--- a/src/root/DeleteList.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { useSelector } from "react-redux"
-import { BsTrashFill as DeleteIcon } from "react-icons/bs"
-import { FaFileExport as ExportIcon } from "react-icons/fa"
-import AnonIcon from "../ui/AnonIcon"
-import { RootState } from "../store"
-
-const DeleteList = () => {
- const deleteList = useSelector((state: RootState) => state.delete.studies)
- const anonList = useSelector((state: RootState) => state.anonymize.studies)
- const exportList = useSelector((state: RootState) => state.export.series)
-
- return (
-
-
- {/* Anon Button */}
-
- alert('Anonymize action triggered!')}
- />
-
- {Object.keys(anonList).length}
-
-
-
- {/* Export Button */}
-
- alert('Export action triggered!')}
- />
-
- {Object.keys(exportList).length}
-
-
-
- {/* Delete Button */}
-
- alert('Delete action triggered!')}
- />
-
- {Object.keys(deleteList).length}
-
-
-
-
- );
-
-}
-
-export default DeleteList
diff --git a/src/root/Header.tsx b/src/root/Header.tsx
index 0fea04b1..8b8a796c 100644
--- a/src/root/Header.tsx
+++ b/src/root/Header.tsx
@@ -11,7 +11,7 @@ import Banner from '../ui/menu/Banner';
import DropDown from '../ui/menu/DropDown';
import ToggleSwitch from '../ui/menu/ToggleSwitch';
import BannerItems from '../ui/menu/BannerItems';
-import DeleteList from './DeleteList';
+import DeleteList from './ToolList';
type Item = {
title: string;
diff --git a/src/root/RootApp.tsx b/src/root/RootApp.tsx
index c1a74126..7e0edf81 100644
--- a/src/root/RootApp.tsx
+++ b/src/root/RootApp.tsx
@@ -12,6 +12,9 @@ import QueryRoot from "../query/QueryRoot";
import ImportCreateRoot from "../import/ImportCreateRoot";
import ContentRoot from "../content/ContentRoot";
import DatasetRoot from "../datasets/DatasetRoot";
+import DeleteRoot from "../delete/DeleteRoot";
+import AnonymizeRoot from "../anonymize/AnonymizeRoot";
+import ExportRoot from "../export/ExportRoot";
const titlePath: { [key: string]: string } = {
"/administration/general": "General",
@@ -71,6 +74,9 @@ const RootApp = () => {
} />
} />
} />
+ } />
+ } />
+ } />
diff --git a/src/root/ToolItem.tsx b/src/root/ToolItem.tsx
new file mode 100644
index 00000000..54f3c6bf
--- /dev/null
+++ b/src/root/ToolItem.tsx
@@ -0,0 +1,17 @@
+type ToolItemProps = {
+ children: React.ReactNode
+ count: number
+}
+
+const ToolItem = ({ children, count }: ToolItemProps) => {
+ return (
+