From 0885b6131b06723ec7f66734f4c14cf54fac48ab Mon Sep 17 00:00:00 2001 From: Sophie Labyt Date: Thu, 26 Sep 2024 15:02:45 +0200 Subject: [PATCH] wip --- src/admin/users/roles/RolesTable.tsx | 1 - src/anonymize/AnonymizeRoot.tsx | 10 +++ src/assets/Anon.svg | 12 +++ src/content/ContentRoot.tsx | 91 +++++++++++++++++++++-- src/content/patients/AccordionPatient.tsx | 21 +++++- src/content/studies/StudyRoot.tsx | 80 +------------------- src/delete/DeleteQueues.tsx | 29 ++++++++ src/delete/DeleteRoot.tsx | 67 +++++++++++++++++ src/delete/DeleteStudyTable.tsx | 67 +++++++++++++++++ src/delete/ProgressQueue.tsx | 25 +++++++ src/export/ExportRoot.tsx | 48 ++++++++++++ src/export/ExportSeriesTable.tsx | 60 +++++++++++++++ src/export/ExportStudyTable.tsx | 65 ++++++++++++++++ src/reducers/DeleteSlice.ts | 8 +- src/reducers/ExportSlice.ts | 23 ++++-- src/root/DeleteList.tsx | 55 -------------- src/root/Header.tsx | 2 +- src/root/RootApp.tsx | 6 ++ src/root/ToolItem.tsx | 17 +++++ src/root/ToolList.tsx | 44 +++++++++++ src/services/queues.ts | 66 ++++++++++++++++ src/ui/FormCard.tsx | 2 +- src/ui/Toolsbar.tsx | 11 --- src/ui/table/Table.tsx | 2 +- src/utils/actionsUtils.ts | 3 +- src/utils/types.ts | 8 ++ src/welcome/SignInForm.tsx | 8 +- 27 files changed, 657 insertions(+), 174 deletions(-) create mode 100644 src/anonymize/AnonymizeRoot.tsx create mode 100644 src/assets/Anon.svg create mode 100644 src/delete/DeleteQueues.tsx create mode 100644 src/delete/DeleteRoot.tsx create mode 100644 src/delete/DeleteStudyTable.tsx create mode 100644 src/delete/ProgressQueue.tsx create mode 100644 src/export/ExportRoot.tsx create mode 100644 src/export/ExportSeriesTable.tsx create mode 100644 src/export/ExportStudyTable.tsx delete mode 100644 src/root/DeleteList.tsx create mode 100644 src/root/ToolItem.tsx create mode 100644 src/root/ToolList.tsx create mode 100644 src/services/queues.ts delete mode 100644 src/ui/Toolsbar.tsx diff --git a/src/admin/users/roles/RolesTable.tsx b/src/admin/users/roles/RolesTable.tsx index 3eb90081..904ed1e5 100644 --- a/src/admin/users/roles/RolesTable.tsx +++ b/src/admin/users/roles/RolesTable.tsx @@ -106,7 +106,6 @@ const RolesTable = ({ data = [], onEdit, onDelete }: RolesTableProps) => { ]; return (
- { + return ( +
+ Hello World Anon +
+ ) +} + + +export default AnonymizeRoot \ No newline at end of file diff --git a/src/assets/Anon.svg b/src/assets/Anon.svg new file mode 100644 index 00000000..61294a7f --- /dev/null +++ b/src/assets/Anon.svg @@ -0,0 +1,12 @@ + + + \ No newline at end of file diff --git a/src/content/ContentRoot.tsx b/src/content/ContentRoot.tsx index d7c5a66f..e92e42b5 100644 --- a/src/content/ContentRoot.tsx +++ b/src/content/ContentRoot.tsx @@ -6,17 +6,27 @@ import { QueryPayload, useCustomMutation, useCustomQuery, useCustomToast, Study import Model from "../model/Model"; import Patient from "../model/Patient"; -import { FormCard, Spinner } from "../ui"; +import { FormCard, Spinner, Button } from "../ui"; import SearchForm from "../query/SearchForm"; import AccordionPatient from "./patients/AccordionPatient"; import EditPatient from "./patients/EditPatient"; import { Label } from "../utils/types"; +import { addStudyIdToDeleteList, addSeriesOfStudyIdToExportList, addStudyIdToAnonymizeList } from '../utils/actionsUtils'; + +import { Colors } from '../utils'; + +import AnonIcon from './../assets/Anon.svg?react'; +import { BsTrashFill as DeleteIcon } from "react-icons/bs"; +import { FaFileExport as ExportIcon } from "react-icons/fa"; + const ContentRoot: React.FC = () => { const { confirm } = useConfirm(); const { toastSuccess, toastError } = useCustomToast(); + const [selectedStudies, setSelectedStudies] = useState<{ [studyId: string]: boolean }>({}); + const [model, setModel] = useState(null); const [queryPayload, setQueryPayload] = useState(null); const [editingPatient, setEditingPatient] = useState(null); @@ -86,14 +96,51 @@ const ContentRoot: React.FC = () => { mutateToolsFind(formData); }; + const handlePatientSelectionChange = (selected: boolean, patient: Patient) => { + const studies = patient.getStudies().map(study => study.id) + const newSelectedStudies = { ...selectedStudies }; + if (selected) { + studies.forEach((studyId) => { + newSelectedStudies[studyId] = true; + }) + setSelectedStudies(newSelectedStudies); + } else { + studies.forEach((studyId) => { + delete newSelectedStudies[studyId]; + }) + setSelectedStudies(newSelectedStudies); + } + } + const refreshFind = () => { if (queryPayload) { mutateToolsFind(queryPayload); } }; + const handleSendAnonymizeList = async () => { + const studyIds = Object.keys(selectedStudies); + for (const studyId of studyIds) { + await addStudyIdToAnonymizeList(studyId); + } + }; + + const handleSendExportList = async () => { + const studyIds = Object.keys(selectedStudies); + for (const studyId of studyIds) { + await addSeriesOfStudyIdToExportList(studyId) + } + }; + + const handleSendDeleteList = async () => { + const studyIds = Object.keys(selectedStudies); + for (const studyId of studyIds) { + await addStudyIdToDeleteList(studyId); + } + }; + return ( -
+
{ show={editingPatient != null} /> -
-
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 ( +
+ {children} + + {count} + +
+ ) +} + +export default ToolItem \ No newline at end of file diff --git a/src/root/ToolList.tsx b/src/root/ToolList.tsx new file mode 100644 index 00000000..71ea6701 --- /dev/null +++ b/src/root/ToolList.tsx @@ -0,0 +1,44 @@ +import { useSelector } from "react-redux" +import { RootState } from "../store" + +import { BsTrashFill as DeleteIcon } from "react-icons/bs" +import { FaFileExport as ExportIcon } from "react-icons/fa" +import AnonIcon from "../ui/AnonIcon" +import ToolItem from "./ToolItem" +import { useNavigate } from "react-router-dom" + +const ToolList = () => { + + const navigate = useNavigate(); + + 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 ( + +
+ + navigate('/anonymize')} + /> + + + navigate('/export')} + /> + + + navigate('/delete')} + /> + +
+ ); + +} + +export default ToolList diff --git a/src/services/queues.ts b/src/services/queues.ts new file mode 100644 index 00000000..1fbd007c --- /dev/null +++ b/src/services/queues.ts @@ -0,0 +1,66 @@ +import axios from "axios"; +import { Queue } from "../utils/types"; + +export const createDeleteQueue = (seriesId: string[]): Promise => { + const payload = { + OrthancSeriesIds: seriesId + } + return axios + .post(`/api/queues/delete`, payload) + .then((response) => response.data.Uuid) + .catch(function (error) { + if (error.response) { + throw error.response; + } + throw error; + }); +}; + +export const getDeleteQueue = (uuid: string): Promise => { + + return axios + .get(`/api/queues/delete/${uuid}`) + .then((response) => { + const data: any = Object.values(response.data)[0]; + return { + userId: data.UserId, + progress: data.Progress, + state: data.State, + id: data.Id, + results: data.Results + } + }) + .catch(function (error) { + if (error.response) { + throw error.response; + } + throw error; + }); +}; + +export const deleteDeleteQueue = (uuid: string): Promise => { + + return axios + .delete(`/api/queues/delete/${uuid}`) + .then(() => undefined) + .catch(function (error) { + if (error.response) { + throw error.response; + } + throw error; + }); +}; + + +export const getExistingDeleteQueues = (userId: number | undefined): Promise => { + const url = userId ? `/api/queues/delete?userId=${userId}` : '/api/queues/delete' + return axios + .get(url) + .then((response) => response.data) + .catch(function (error) { + if (error.response) { + throw error.response; + } + throw error; + }); +}; \ No newline at end of file diff --git a/src/ui/FormCard.tsx b/src/ui/FormCard.tsx index 51c3c73e..a9c62ea2 100644 --- a/src/ui/FormCard.tsx +++ b/src/ui/FormCard.tsx @@ -16,7 +16,7 @@ const FormCard = ({ title, onSubmit, children, className, collapsible = false, o const [isCollapsed, setIsCollapsed] = useState(false); return ( - +
{collapsible && ( diff --git a/src/ui/Toolsbar.tsx b/src/ui/Toolsbar.tsx deleted file mode 100644 index b1ef2af7..00000000 --- a/src/ui/Toolsbar.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -const Toolsbar = ({ children }) => { - return ( -
- {children} -
- ); -}; - -export default Toolsbar; diff --git a/src/ui/table/Table.tsx b/src/ui/table/Table.tsx index 0ccd8fec..e40c131b 100644 --- a/src/ui/table/Table.tsx +++ b/src/ui/table/Table.tsx @@ -59,7 +59,7 @@ function Table({ }: TableProps) { const [sorting, setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]); - const [rowSelection, setRowSelection] = useState({}) + const [rowSelection, setRowSelection] = useState(selectedRow) const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: pageSize, diff --git a/src/utils/actionsUtils.ts b/src/utils/actionsUtils.ts index 93364e24..9f5a7467 100644 --- a/src/utils/actionsUtils.ts +++ b/src/utils/actionsUtils.ts @@ -26,9 +26,10 @@ export const addStudyIdToAnonymizeList = async (studyId: string) => { } export const addSeriesOfStudyIdToExportList = async (studyId: string) => { + const study = await getStudy(studyId) const series = await getSeriesOfStudy(studyId) series.forEach(series => - store.dispatch(addSeriesToExportList({ series: series })) + store.dispatch(addSeriesToExportList({ series: series, study: study })) ) } diff --git a/src/utils/types.ts b/src/utils/types.ts index a8dc7216..d0687acb 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -390,3 +390,11 @@ export type SeriesModifyPayload = { synchronous: boolean; keep : string[]; }; + +export type Queue = { + progress : number + state : string + id : string + results : Record + userId : number +} \ No newline at end of file diff --git a/src/welcome/SignInForm.tsx b/src/welcome/SignInForm.tsx index 61c8ebd1..c106fdf1 100644 --- a/src/welcome/SignInForm.tsx +++ b/src/welcome/SignInForm.tsx @@ -62,15 +62,15 @@ export const SignInForm = () => { }; return ( -
+
{/* Background square */} -
+
-
+

{t("titleSignInForm")} -

) +

Please Log in to your Account