From 441c93ac100e85444302ae90cd0ce83f0d01c1be Mon Sep 17 00:00:00 2001 From: Sophie Labyt Date: Thu, 17 Oct 2024 15:08:01 +0200 Subject: [PATCH 1/6] Autofill drop-down button and add progress queues cards components to home --- src/anonymize/AnonQueues.tsx | 54 ++++++------- src/anonymize/AnonymizeRoot.tsx | 120 +++++++++++++++------------- src/anonymize/StudyTable.tsx | 40 ++++++---- src/export/ExportRoot.tsx | 106 ++++++++++-------------- src/export/ExportSeriesTable.tsx | 38 ++++++--- src/export/ExportStudyTable.tsx | 35 +++++--- src/root/Dashboard.tsx | 67 ---------------- src/root/Dashboard/CardAnon.tsx | 23 ++++++ src/root/Dashboard/CardExport.tsx | 21 +++++ src/root/Dashboard/Cardretrieve.tsx | 21 +++++ src/root/Dashboard/Dashboard.tsx | 38 +++++++++ src/root/RootApp.tsx | 2 +- src/ui/Card.tsx | 2 +- 13 files changed, 310 insertions(+), 257 deletions(-) delete mode 100644 src/root/Dashboard.tsx create mode 100644 src/root/Dashboard/CardAnon.tsx create mode 100644 src/root/Dashboard/CardExport.tsx create mode 100644 src/root/Dashboard/Cardretrieve.tsx create mode 100644 src/root/Dashboard/Dashboard.tsx diff --git a/src/anonymize/AnonQueues.tsx b/src/anonymize/AnonQueues.tsx index c818994..057f2dd 100644 --- a/src/anonymize/AnonQueues.tsx +++ b/src/anonymize/AnonQueues.tsx @@ -7,13 +7,14 @@ import ProgressQueueBar from "../queue/ProgressQueueBar"; import { AnonQueue } from "../utils/types"; import AnonymizeResultTable from "./AnonymizeResultTable"; import { useMemo } from "react"; +import ProgressQueueCircle from "../queue/ProgressQueueCircle"; type AnonQueuesProps = { - showResults: boolean -} - -const AnonQueues = ({ showResults }: AnonQueuesProps) => { + showResults: boolean; + circle?: boolean; +}; +const AnonQueues = ({ showResults, circle }: AnonQueuesProps) => { const currentUserId = useSelector((state: RootState) => state.user.currentUserId); const { data: existingAnonymizeQueues } = useCustomQuery( @@ -21,14 +22,14 @@ const AnonQueues = ({ showResults }: AnonQueuesProps) => { () => getExistingAnonymizeQueues(currentUserId) ); - const firstQueue = existingAnonymizeQueues?.[0] + const firstQueue = existingAnonymizeQueues?.[0]; const { data, isPending, isFetching } = useCustomQuery( ['queue', 'anonymize', firstQueue], () => getAnonymizeQueue(firstQueue), { refetchInterval: 2000, - enabled: (firstQueue != null) + enabled: firstQueue != null, } ); @@ -38,37 +39,32 @@ const AnonQueues = ({ showResults }: AnonQueuesProps) => { ); const anonymizedStudies = useMemo(() => { - if (!data) return [] - return (data).map(job => job.results) - }, [data]) - + if (!data) return []; + return data.map(job => job.results); + }, [data]); const globalProgress = useMemo(() => { - if (!data) return 0 - const totalProgress = data.map(job => job.state !== 'wait' ? 1 : 0) - const sumProgress = totalProgress.reduce((accumulator, currentValue) => { - return accumulator + currentValue - }, 0); - return (sumProgress / data.length) * 100 - }, [data]) + if (!data) return 0; + const totalProgress = data.map(job => (job.state !== 'wait' ? 1 : 0)); + const sumProgress = totalProgress.reduce((accumulator, currentValue) => accumulator + currentValue, 0); + return (sumProgress / data.length) * 100; + }, [data]); - //If no queue, nothing to show - if (!firstQueue) return null - //if awaiting value show spinner + // If no queue, nothing to show + if (!firstQueue) return null; + // If awaiting value, show spinner if (isPending || isFetching) return ; return (
-
- +
+ {circle ? ( + + ) : ( + + )}
- { - showResults && ( - - ) - } + {showResults && }
); }; diff --git a/src/anonymize/AnonymizeRoot.tsx b/src/anonymize/AnonymizeRoot.tsx index 8f4135f..9ce26ae 100644 --- a/src/anonymize/AnonymizeRoot.tsx +++ b/src/anonymize/AnonymizeRoot.tsx @@ -27,10 +27,7 @@ const profileOptions = [ const AnonymizeRoot = () => { const dispatch = useDispatch(); const anonList = useSelector((state: RootState) => state.anonymize); - const [selectedPatientId, setSelectedPatientId] = useState( - null - ); - + const [selectedPatientId, setSelectedPatientId] = useState(null); const [anonJobId, setAnonJobId] = useState(null); const { mutate: mutateCreateAnonymizeQueue } = useCustomMutation( @@ -41,10 +38,9 @@ const AnonymizeRoot = () => { setAnonJobId(jobId) }, } - ) + ); const patients = useMemo(() => Object.values(anonList.patients), [anonList]); - const studies = useMemo(() => { if (!selectedPatientId) return []; return Object.values(anonList.studies).filter( @@ -77,33 +73,31 @@ const AnonymizeRoot = () => { const onChangeStudy = (studyId, key, value) => { dispatch( updateAnonymizeStudyValue({ studyId, [key]: value }) - ) - } + ); + }; const onRemoveStudy = (studyId) => { - dispatch(removeStudyFromAnonymizeList({ studyId })) - } + dispatch(removeStudyFromAnonymizeList({ studyId })); + }; const onChangePatient = (patientId, key, value) => { dispatch( updateAnonymizePatientValue({ patientId, [key]: value }) - ) - } + ); + }; const onRemovePatient = (patientId) => { studies.filter((study) => study.originalStudy.parentPatient === patientId). forEach((study) => { dispatch( removeStudyFromAnonymizeList({ studyId: study.originalStudy.id }) - ) - } - ) - - } + ); + }); + }; const onChangeProfile = (option) => { - dispatch(updateAnonymizationProfile({ anonymizationProfile: option.value })) - } + dispatch(updateAnonymizationProfile({ anonymizationProfile: option.value })); + }; const handleAnonymizeStart = () => { const anonItems: AnonItem[] = Object.values(anonList.studies).map((study) => { @@ -114,53 +108,64 @@ const AnonymizeRoot = () => { NewPatientName: study.newPatientName, NewStudyDescription: study.newStudyDescription, NewAccessionNumber: study.newAccessionNumber - } - - }) - mutateCreateAnonymizeQueue({ anonItems }) - } + }; + }); + mutateCreateAnonymizeQueue({ anonItems }); + }; return ( -
-
- Anonymize resources
-
- - - - - +
+
+ Anonymize resources +
+
+ + + Auto Fill +
+ } + className="mr-4" + > +
+ +
+ + Auto +
+ +
- + + -
-
+
{
- -
+ +
@@ -193,6 +198,7 @@ const AnonymizeRoot = () => { value={anonList.anonymizationProfile} options={profileOptions} onChange={onChangeProfile} + className="w-full sm:w-auto" />
diff --git a/src/anonymize/StudyTable.tsx b/src/anonymize/StudyTable.tsx index 3a0f085..af069dd 100644 --- a/src/anonymize/StudyTable.tsx +++ b/src/anonymize/StudyTable.tsx @@ -6,11 +6,12 @@ import { AnonStudy } from "../utils/types"; type StudyTableProps = { studies: AnonStudy[]; - onChangeStudy: (studyId: string, key:string, value: string) => void; + onChangeStudy: (studyId: string, key: string, value: string) => void; onRemoveStudy: (studyId: string) => void; + selectedRows?: Record; }; -const StudyTable = ({ studies, onChangeStudy, onRemoveStudy }: StudyTableProps) => { +const StudyTable = ({ studies, onChangeStudy, onRemoveStudy, selectedRows }: StudyTableProps) => { const columns: ColumnDef[] = [ { id: "id", @@ -25,7 +26,7 @@ const StudyTable = ({ studies, onChangeStudy, onRemoveStudy }: StudyTableProps) header: "Study Description", }, { - id : "newStudyDescription", + id: "newStudyDescription", accessorKey: "newStudyDescription", header: "New Study Description", isEditable: true, @@ -36,9 +37,7 @@ const StudyTable = ({ studies, onChangeStudy, onRemoveStudy }: StudyTableProps) return ( @@ -47,17 +46,26 @@ const StudyTable = ({ studies, onChangeStudy, onRemoveStudy }: StudyTableProps) }, ]; - return row.originalStudy.id} - - />; + const getRowClasses = (row: AnonStudy) => { + if (selectedRows?.[row.originalStudy.id]) { + return 'bg-primary hover:cursor-pointer'; + } else { + return 'hover:bg-indigo-100 hover:cursor-pointer'; + } + }; + return ( +
row.originalStudy.id} + getRowClasses={getRowClasses} + /> + ); }; export default StudyTable; diff --git a/src/export/ExportRoot.tsx b/src/export/ExportRoot.tsx index 779cf87..17dac4b 100644 --- a/src/export/ExportRoot.tsx +++ b/src/export/ExportRoot.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import Papa from "papaparse" +import Papa from "papaparse"; import { RootState } from "../store"; import ExportStudyTable from "./ExportStudyTable"; import ExportSeriesTable from "./ExportSeriesTable"; @@ -19,7 +19,6 @@ import SelectTransferSyntax from "./SelectTransferSyntax"; import { Download } from "../icons"; import Empty from "../icons/Empty"; - const ExportRoot = () => { const { toastSuccess, updateExistingToast, toastWarning } = useCustomToast(); const dispatch = useDispatch(); @@ -29,22 +28,15 @@ const ExportRoot = () => { const [currentStudyId, setCurrentStudyId] = useState(null); const [storeJobId, setStoreJobId] = useState(null); const [sendPeerJobId, setsendPeerJobId] = useState(null); - const [transferSyntax, setTrasferSyntax] = useState('None') + const [transferSyntax, setTrasferSyntax] = useState('None'); const series = useMemo(() => { if (!currentStudyId) return []; return Object.values(exportSeriesList).filter((series) => series.parentStudy === currentStudyId); }, [currentStudyId, exportSeriesList]); - const { data: modalities } = useCustomQuery( - ["modalities"], - () => getModalities() - ); - - const { data: peers } = useCustomQuery( - ["peers"], - () => getPeers() - ); + const { data: modalities } = useCustomQuery(["modalities"], () => getModalities()); + const { data: peers } = useCustomQuery(["peers"], () => getPeers()); const { mutate: storeMutate } = useCustomMutation( ({ modalityName, resources }) => storeToModality(modalityName, resources), @@ -82,7 +74,7 @@ const ExportRoot = () => { (mb) => updateExistingToast(id, "Downloaded " + mb + " mb"), undefined, hierarchical, - transferSyntax != "None" ? transferSyntax : undefined + transferSyntax !== "None" ? transferSyntax : undefined ); }; @@ -97,14 +89,14 @@ const ExportRoot = () => { }; const handleDownloadCsv = () => { - const series = Object.values(exportSeriesList) + const series = Object.values(exportSeriesList); if (series.length === 0) { toastWarning("Empty export list"); return; } const exportData = series.map(series => { - const study = exportStudyList[series.parentStudy] + const study = exportStudyList[series.parentStudy]; return { ...study.patientMainDicomTags, ...study.mainDicomTags, @@ -113,11 +105,11 @@ const ExportRoot = () => { orthancSeriesId: series.id, orthancStudyId: series.parentStudy, orthancPatientId: study.parentPatient - } - }) - const csvString = Papa.unparse(exportData, {}) - exportCsv(csvString, '.csv', 'export-list.csv') - } + }; + }); + const csvString = Papa.unparse(exportData, {}); + exportCsv(csvString, '.csv', 'export-list.csv'); + }; const downloadOptions = [ { @@ -135,32 +127,25 @@ const ExportRoot = () => { ]; const modalitiesOptions = useMemo(() => { - return modalities?.map((modality) => { - return { - label: modality.name, - action: () => handleExportToModality(modality.name), - }; - }) ?? []; + return modalities?.map((modality) => ({ + label: modality.name, + action: () => handleExportToModality(modality.name), + })) ?? []; }, [modalities]); const peersOptions = useMemo(() => { - return peers?.map((peer) => { - return { - label: peer.name, - action: () => handleExportToPeer(peer.name), - }; - }) ?? []; + return peers?.map((peer) => ({ + label: peer.name, + action: () => handleExportToPeer(peer.name), + })) ?? []; }, [peers]); return ( - +
-
Export Ressources
+
Export Resources
- - - setTrasferSyntax(value)} + setTrasferSyntax(value)} /> -
-
- -
-
+ +
+
- + studies={Object.values(exportStudyList)} + /> +
+
- -
+ +
- - {storeJobId && } - + {storeJobId && } + {sendPeerJobId && } @@ -221,8 +201,6 @@ const ExportRoot = () => { ); - - }; -export default ExportRoot; +export default ExportRoot; \ No newline at end of file diff --git a/src/export/ExportSeriesTable.tsx b/src/export/ExportSeriesTable.tsx index 0fc5aa6..bd6d4ee 100644 --- a/src/export/ExportSeriesTable.tsx +++ b/src/export/ExportSeriesTable.tsx @@ -1,5 +1,5 @@ import { ColumnDef } from "@tanstack/react-table"; -import { Button, Table } from "../ui" +import { Button, Table } from "../ui"; import { Colors, Series } from "../utils"; import { useMemo } from "react"; import { useDispatch } from "react-redux"; @@ -7,13 +7,15 @@ import { removeSeriesFromExportList } from "../reducers/ExportSlice"; import { Trash } from "../icons"; type ExportSeriesTableProps = { - series: Series[] -} -const ExportSeriesTable = ({ series }: ExportSeriesTableProps) => { - const dispatch = useDispatch() + series: Series[]; + selectedRows?: Record; +}; + +const ExportSeriesTable = ({ series, selectedRows }: ExportSeriesTableProps) => { + const dispatch = useDispatch(); const handleDelete = (seriesId: string) => { - dispatch(removeSeriesFromExportList({seriesId : seriesId})) + dispatch(removeSeriesFromExportList({ seriesId })); }; const columns: ColumnDef[] = useMemo( @@ -35,7 +37,7 @@ const ExportSeriesTable = ({ series }: ExportSeriesTableProps) => { header: "Series Number", }, { - accessorFn: (row) => row.instances.length, + accessorFn: (row) => row.instances.length, header: "Instances", }, { @@ -55,9 +57,23 @@ const ExportSeriesTable = ({ series }: ExportSeriesTableProps) => { [] ); + const getRowClasses = (row: Series) => { + if (selectedRows?.[row.id]) { + return 'bg-primary hover:cursor-pointer'; + } else { + return 'hover:bg-indigo-100 hover:cursor-pointer'; + } + }; + return ( -
- ) -} +
+ ); +}; -export default ExportSeriesTable \ No newline at end of file +export default ExportSeriesTable; diff --git a/src/export/ExportStudyTable.tsx b/src/export/ExportStudyTable.tsx index 4582ef6..5033291 100644 --- a/src/export/ExportStudyTable.tsx +++ b/src/export/ExportStudyTable.tsx @@ -1,7 +1,6 @@ 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 { removeSeriesFromExportList } from "../reducers/ExportSlice"; @@ -9,16 +8,17 @@ import { Trash } from "../icons"; type ExportStudyTableProps = { studies: Study[]; - onClickStudy: (study: Study) => void + selectedRows?: Record; + onClickStudy: (study: Study) => void; }; -const ExportStudyTable = ({ studies, onClickStudy }: ExportStudyTableProps) => { +const ExportStudyTable = ({ studies, selectedRows, onClickStudy }: ExportStudyTableProps) => { const dispatch = useDispatch(); const handleDelete = (studyId: string) => { - const studyToDelete = studies.find(study => study.id === studyId) + const studyToDelete = studies.find(study => study.id === studyId); for (const seriesId of studyToDelete.series) { - dispatch(removeSeriesFromExportList({ seriesId: seriesId })) + dispatch(removeSeriesFromExportList({ seriesId })); } }; @@ -65,12 +65,25 @@ const ExportStudyTable = ({ studies, onClickStudy }: ExportStudyTableProps) => { [] ); - return
; + const getRowClasses = (row: Study) => { + if (selectedRows?.[row.id]) { + return 'bg-primary hover:cursor-pointer'; + } else { + return 'hover:bg-indigo-100 hover:cursor-pointer'; + } + }; + + return ( +
+ ); }; export default ExportStudyTable; diff --git a/src/root/Dashboard.tsx b/src/root/Dashboard.tsx deleted file mode 100644 index 2804d2c..0000000 --- a/src/root/Dashboard.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import Button from "../ui/Button"; -import Card, { CardHeader, CardBody, CardFooter } from "../ui/Card"; -import { Colors } from "../utils/enums"; - -const Dashboard = () => { - const username = "M.Ohma"; - - return ( -
-

Overview

-
-

- {username} -

- Hello Image -
- -
- {/* Card Anonymisation */} - - - -

Progress

-
- - - -
- - {/* Card Delete */} - - - -

Progress

-
- - - -
- - {/* Card Retrieve */} - - - -

Retrieve

-
- - - -
- -
-
- ); -}; - -export default Dashboard; diff --git a/src/root/Dashboard/CardAnon.tsx b/src/root/Dashboard/CardAnon.tsx new file mode 100644 index 0000000..62fe9c5 --- /dev/null +++ b/src/root/Dashboard/CardAnon.tsx @@ -0,0 +1,23 @@ +import Button from "../../ui/Button"; +import Card, { CardHeader, CardBody, CardFooter } from "../../ui/Card"; +import AnonQueues from "../../anonymize/AnonQueues"; +import { Colors } from "../../utils/enums"; + +const CardAnon = () => { + return ( + + + + + + + + + + ); +}; + +export default CardAnon; diff --git a/src/root/Dashboard/CardExport.tsx b/src/root/Dashboard/CardExport.tsx new file mode 100644 index 0000000..2c72f47 --- /dev/null +++ b/src/root/Dashboard/CardExport.tsx @@ -0,0 +1,21 @@ +import Button from "../../ui/Button"; +import Card, { CardHeader, CardBody, CardFooter } from "../../ui/Card"; +import { Colors } from "../../utils/enums"; + +const CardExport = () => { + return ( + + + +

Progress

+
+ + + +
+ ); +}; + +export default CardExport; diff --git a/src/root/Dashboard/Cardretrieve.tsx b/src/root/Dashboard/Cardretrieve.tsx new file mode 100644 index 0000000..2893bdc --- /dev/null +++ b/src/root/Dashboard/Cardretrieve.tsx @@ -0,0 +1,21 @@ +import Button from "../../ui/Button"; +import Card, { CardHeader, CardBody, CardFooter } from "../../ui/Card"; +import { Colors } from "../../utils/enums"; + +const Cardretrieve = () => { + return ( + + + +

Progress

+
+ + + +
+ ); +}; + +export default Cardretrieve; diff --git a/src/root/Dashboard/Dashboard.tsx b/src/root/Dashboard/Dashboard.tsx new file mode 100644 index 0000000..6ff7257 --- /dev/null +++ b/src/root/Dashboard/Dashboard.tsx @@ -0,0 +1,38 @@ +// src/components/Dashboard.js + +import CardAnon from "./CardAnon"; +import CardExport from "./CardExport"; +import Cardretrieve from "./Cardretrieve"; +const Dashboard = () => { + const username = "M.Ohma"; + + return ( +
+

Overview

+
+

+ {username} +

+ Hello Image +
+ +
+ {/* Card Anonymisation */} + + + {/* Card Export */} + + + {/* Card Retrieve */} + + +
+
+ ); +}; + +export default Dashboard; diff --git a/src/root/RootApp.tsx b/src/root/RootApp.tsx index 8e8bc3a..b9efe3a 100644 --- a/src/root/RootApp.tsx +++ b/src/root/RootApp.tsx @@ -5,7 +5,7 @@ import { Route, Routes, useLocation, useNavigate } from "react-router-dom"; import { logout } from "../reducers/UserSlice"; import SideBar from "./SideBar"; -import Dashboard from "./Dashboard"; +import Dashboard from "./Dashboard/Dashboard"; import AdminRoot from "../admin/AdminRoot"; import Header from "./Header"; import QueryRoot from "../query/QueryRoot"; diff --git a/src/ui/Card.tsx b/src/ui/Card.tsx index 4261d33..5174e06 100644 --- a/src/ui/Card.tsx +++ b/src/ui/Card.tsx @@ -40,9 +40,9 @@ const colorClasses: Record = { warning: "bg-warning", dark: "bg-dark", gray: "bg-gray", + blueCustom: "bg-blue-custom", light: "bg-light", white: "bg-white", - [Colors.blueCustom]: '', [Colors.lightGray]: '' }; From c79122c69629cfc27450efa367e695bf625cd926 Mon Sep 17 00:00:00 2001 From: Sophie Labyt Date: Tue, 22 Oct 2024 09:38:43 +0200 Subject: [PATCH 2/6] correction progress circle --- src/admin/modalities/ModalitiesTable.tsx | 8 +- src/delete/DeleteRoot.tsx | 1 + src/queue/ProgressQueueCircle.tsx | 17 ++-- src/root/Dashboard/CardAnon.tsx | 54 ++++++++++-- src/ui/ProgressCircle.tsx | 104 ++++++++++++----------- 5 files changed, 121 insertions(+), 63 deletions(-) diff --git a/src/admin/modalities/ModalitiesTable.tsx b/src/admin/modalities/ModalitiesTable.tsx index bc58b40..6756b85 100644 --- a/src/admin/modalities/ModalitiesTable.tsx +++ b/src/admin/modalities/ModalitiesTable.tsx @@ -18,7 +18,7 @@ const ModalitiesTable: React.FC = ({ onDeleteAet, onEchoAet, }) => { - + const columns: ColumnDef[] = [ { accessorKey: "name", @@ -41,7 +41,7 @@ const ModalitiesTable: React.FC = ({ header: "Actions", id: "actions", cell: ({ row }) => ( -
+
{ [[]], { onSuccess: (uuid) => { + console.log("Queue created with UUID:", uuid); // Log pour vérifier l'UUID setQueueUuid(uuid); }, } diff --git a/src/queue/ProgressQueueCircle.tsx b/src/queue/ProgressQueueCircle.tsx index 676488e..90ac0ad 100644 --- a/src/queue/ProgressQueueCircle.tsx +++ b/src/queue/ProgressQueueCircle.tsx @@ -3,18 +3,23 @@ import { Cancel } from "../icons"; import { Queue } from "../utils/types"; type ProgressQueueProps = { - queueData: Queue, - onDelete: (event: React.MouseEvent) => void + queueData: Queue; + onDelete: (event: React.MouseEvent) => void; + colors: { background: string; progress: string }; // Ajout des couleurs }; -const ProgressQueueCircle = ({ queueData, onDelete }: ProgressQueueProps) => { - +const ProgressQueueCircle = ({ queueData, onDelete, colors }: ProgressQueueProps) => { return (
- +
diff --git a/src/root/Dashboard/CardAnon.tsx b/src/root/Dashboard/CardAnon.tsx index 62fe9c5..205524d 100644 --- a/src/root/Dashboard/CardAnon.tsx +++ b/src/root/Dashboard/CardAnon.tsx @@ -1,19 +1,63 @@ import Button from "../../ui/Button"; import Card, { CardHeader, CardBody, CardFooter } from "../../ui/Card"; -import AnonQueues from "../../anonymize/AnonQueues"; -import { Colors } from "../../utils/enums"; +import { Colors } from "../../utils/enums"; // Assurez-vous que `Colors.primary` est défini ici +import { useSelector } from "react-redux"; +import { getExistingAnonymizeQueues, getAnonymizeQueue } from "../../services/queues"; +import { useCustomQuery } from "../../utils"; +import { useMemo } from "react"; +import ProgressQueueCircle from "../../queue/ProgressQueueCircle"; +import { Spinner } from "../../ui"; +import { AnonQueue } from "../../utils/types"; const CardAnon = () => { + const currentUserId = useSelector((state) => state.user.currentUserId); + + const { data: existingAnonymizeQueues } = useCustomQuery( + ['queue', 'anonymize', currentUserId?.toString() || ''], + () => getExistingAnonymizeQueues(currentUserId) + ); + + const firstQueue = existingAnonymizeQueues?.[0]; + + const { data, isPending, isFetching } = useCustomQuery( + ['queue', 'anonymize', firstQueue], + () => getAnonymizeQueue(firstQueue), + { + refetchInterval: 2000, + enabled: firstQueue != null, + } + ); + + const globalProgress = useMemo(() => { + if (!data || data.length === 0) return 0; + const totalJobs = data.length; + const completedJobs = data.filter(job => job.state === 'completed' || job.state === 'in progress').length; + + return totalJobs === 0 ? 0 : (completedJobs / totalJobs) * 100; + }, [data]); + + if (!firstQueue) return null; + if (isPending || isFetching) return ; + return ( - - + { }} + queueData={{ + progress: globalProgress, + state: "", + id: "", + results: undefined, + userId: 0 + }} + colors={{ background: 'text-gray-300', progress: Colors.primary }} /> diff --git a/src/ui/ProgressCircle.tsx b/src/ui/ProgressCircle.tsx index 2ecb3b6..76d2048 100644 --- a/src/ui/ProgressCircle.tsx +++ b/src/ui/ProgressCircle.tsx @@ -1,57 +1,65 @@ type ProgressCircleProps = { - progress: number; - text?: string; - size?: number; // This defines the size of the circle - children?: React.ReactNode; + progress: number; + text?: string; + size?: number; // This defines the size of the circle + children?: React.ReactNode; + className?: string; // Prop for custom CSS class + progressColor?: string; // Ajout pour la couleur de progression }; -const ProgressCircle = ({ progress, text = '%', size = 100, children }: ProgressCircleProps) => { // Reduced default size - const radius = 12; // Reduced radius for a smaller circle - const circumference = 2 * Math.PI * radius; - const progressValue = (progress / 100) * circumference; +const ProgressCircle = ({ + progress, + text = 'Average', + size = 120, + children, + className = '', // Default to an empty string if no class is provided + progressColor = 'text-orange-600', // Couleur de progression par défaut +}: ProgressCircleProps) => { + const radius = 15; // Increased radius for a larger circle + const circumference = 2 * Math.PI * radius; + const progressValue = (progress / 100) * circumference; - return ( -
- - {/* Background circle */} - + return ( +
+ + {/* Background circle */} + - {/* Progress circle */} - - + {/* Progress circle */} + + - {/* Centered text */} -
- {/* Reduced text size */} - {progress} - - {text} - - {children} - -
-
- ); + {/* Centered text in black color */} +
+ + {progress} + + + {text} + + {children} +
+
+ ); }; export default ProgressCircle; From bf8686323cd651cc2363d42008ed38e24824d3e2 Mon Sep 17 00:00:00 2001 From: Sophie Labyt Date: Tue, 22 Oct 2024 15:09:49 +0200 Subject: [PATCH 3/6] correction style --- src/admin/general/OrthancCard.tsx | 14 ++- src/admin/modalities/ModalitiesTable.tsx | 9 +- src/anonymize/AnonQueues.tsx | 66 +++++++--- src/index.css | 9 +- src/root/Dashboard/CardAnon.tsx | 32 +++-- src/root/SideBar.tsx | 152 ++++++++++------------- src/ui/SelectInput.tsx | 2 +- src/ui/ToggleEye.tsx | 5 +- src/ui/table/FilterTable.tsx | 21 ++-- src/ui/table/Table.tsx | 23 ++-- 10 files changed, 183 insertions(+), 150 deletions(-) diff --git a/src/admin/general/OrthancCard.tsx b/src/admin/general/OrthancCard.tsx index 70a27e9..0ac7e32 100644 --- a/src/admin/general/OrthancCard.tsx +++ b/src/admin/general/OrthancCard.tsx @@ -80,18 +80,22 @@ const OrthancSettingsCard = ({ orthancData }: OrthancCardProps) => { cell: (row: any) => { const [show, setShow] = useState(false); return ( -
- + + value={row.getValue()} + /> setShow(visible)} />
); }, header: 'Password' }, + ]; + const handleSelectChange = (selectedOption: any) => { mutateVerbosity({ level: selectedOption.value }); @@ -116,7 +120,7 @@ const OrthancSettingsCard = ({ orthancData }: OrthancCardProps) => {
diff --git a/src/admin/modalities/ModalitiesTable.tsx b/src/admin/modalities/ModalitiesTable.tsx index 6756b85..dfbb14a 100644 --- a/src/admin/modalities/ModalitiesTable.tsx +++ b/src/admin/modalities/ModalitiesTable.tsx @@ -1,7 +1,5 @@ import React from "react"; - import { ColumnDef } from "@tanstack/react-table"; - import { Table, Badge, Button } from "../../ui"; import { Colors } from "../../utils/enums"; import { Modality } from "../../utils/types"; @@ -41,7 +39,7 @@ const ModalitiesTable: React.FC = ({ header: "Actions", id: "actions", cell: ({ row }) => ( -
+
= ({ className="bg-gray-100" enableColumnFilters enableSorting + getRowClasses={getRowClasses} /> ); }; diff --git a/src/anonymize/AnonQueues.tsx b/src/anonymize/AnonQueues.tsx index 057f2dd..5175571 100644 --- a/src/anonymize/AnonQueues.tsx +++ b/src/anonymize/AnonQueues.tsx @@ -6,7 +6,7 @@ import { Spinner } from "../ui"; import ProgressQueueBar from "../queue/ProgressQueueBar"; import { AnonQueue } from "../utils/types"; import AnonymizeResultTable from "./AnonymizeResultTable"; -import { useMemo } from "react"; +import { useMemo, useEffect, useState } from "react"; import ProgressQueueCircle from "../queue/ProgressQueueCircle"; type AnonQueuesProps = { @@ -28,8 +28,35 @@ const AnonQueues = ({ showResults, circle }: AnonQueuesProps) => { ['queue', 'anonymize', firstQueue], () => getAnonymizeQueue(firstQueue), { - refetchInterval: 2000, - enabled: firstQueue != null, + enabled: !!firstQueue, + } + ); + + const globalProgress = useMemo(() => { + if (!data || data.length === 0) return 0; + const completedJobs = data.filter(job => job.state !== 'wait'); + return (completedJobs.length / data.length) * 100 + }, [data]); + + const [shouldRefetch, setShouldRefetch] = useState(true); + + useEffect(() => { + if (globalProgress < 100) { + const interval = setInterval(() => { + setShouldRefetch(true); + }, 2000); + return () => clearInterval(interval); + } else { + setShouldRefetch(false); + } + }, [globalProgress]); + + const { data: queuedData } = useCustomQuery( + ['queue', 'anonymize', firstQueue], + () => getAnonymizeQueue(firstQueue), + { + enabled: shouldRefetch && !!firstQueue, + refetchInterval: shouldRefetch ? 2000 : false, } ); @@ -39,27 +66,34 @@ const AnonQueues = ({ showResults, circle }: AnonQueuesProps) => { ); const anonymizedStudies = useMemo(() => { - if (!data) return []; - return data.map(job => job.results); - }, [data]); + if (!queuedData) return []; + return queuedData.map(job => job.results); + }, [queuedData]); - const globalProgress = useMemo(() => { - if (!data) return 0; - const totalProgress = data.map(job => (job.state !== 'wait' ? 1 : 0)); - const sumProgress = totalProgress.reduce((accumulator, currentValue) => accumulator + currentValue, 0); - return (sumProgress / data.length) * 100; - }, [data]); - - // If no queue, nothing to show + // If no queue, nothing to display if (!firstQueue) return null; - // If awaiting value, show spinner + + // If pending, display the spinner if (isPending || isFetching) return ; return (
{circle ? ( - + ) : ( )} diff --git a/src/index.css b/src/index.css index 301f610..4452b47 100644 --- a/src/index.css +++ b/src/index.css @@ -1,11 +1,11 @@ @tailwind base; @tailwind components; @tailwind utilities; - + @layer utilities { .custom-scrollbar { scrollbar-width: thin; - scrollbar-color: #4a5568 #edf2f7; + scrollbar-color: #ffffff #edf2f7; } .custom-scrollbar::-webkit-scrollbar { width: 8px; @@ -14,10 +14,9 @@ background: #edf2f7; } .custom-scrollbar::-webkit-scrollbar-thumb { - background-color: #4a5568; + background-color: #ffffff; border-radius: 9999px; border: 2px solid #edf2f7; } - } - + } \ No newline at end of file diff --git a/src/root/Dashboard/CardAnon.tsx b/src/root/Dashboard/CardAnon.tsx index 205524d..1da0ef6 100644 --- a/src/root/Dashboard/CardAnon.tsx +++ b/src/root/Dashboard/CardAnon.tsx @@ -1,6 +1,6 @@ import Button from "../../ui/Button"; import Card, { CardHeader, CardBody, CardFooter } from "../../ui/Card"; -import { Colors } from "../../utils/enums"; // Assurez-vous que `Colors.primary` est défini ici +import { Colors } from "../../utils/enums"; import { useSelector } from "react-redux"; import { getExistingAnonymizeQueues, getAnonymizeQueue } from "../../services/queues"; import { useCustomQuery } from "../../utils"; @@ -12,6 +12,7 @@ import { AnonQueue } from "../../utils/types"; const CardAnon = () => { const currentUserId = useSelector((state) => state.user.currentUserId); + // Récupération des files d'attente existantes const { data: existingAnonymizeQueues } = useCustomQuery( ['queue', 'anonymize', currentUserId?.toString() || ''], () => getExistingAnonymizeQueues(currentUserId) @@ -19,38 +20,49 @@ const CardAnon = () => { const firstQueue = existingAnonymizeQueues?.[0]; + // Récupération des données de la première file d'attente const { data, isPending, isFetching } = useCustomQuery( ['queue', 'anonymize', firstQueue], () => getAnonymizeQueue(firstQueue), { - refetchInterval: 2000, - enabled: firstQueue != null, + refetchInterval: 2000, // Initialement défini, sera remplacé par globalProgress + enabled: !!firstQueue, } ); + // Calcul de la progression globale const globalProgress = useMemo(() => { if (!data || data.length === 0) return 0; + const totalJobs = data.length; const completedJobs = data.filter(job => job.state === 'completed' || job.state === 'in progress').length; - return totalJobs === 0 ? 0 : (completedJobs / totalJobs) * 100; + return (totalJobs === 0) ? 0 : (completedJobs / totalJobs) * 100; }, [data]); + // Réinitialiser le refetchInterval si la progression atteint 100 + useCustomQuery(['queue', 'anonymize', firstQueue], () => getAnonymizeQueue(firstQueue), { + refetchInterval: globalProgress < 100 ? 2000 : false, + enabled: !!firstQueue, + }); + + // Si aucune file d'attente n'est trouvée, ne rien afficher if (!firstQueue) return null; + // Si les données sont en cours de chargement, afficher un spinner if (isPending || isFetching) return ; - + return ( { }} + onDelete={() => { }} // Ajoutez votre fonction de suppression ici queueData={{ progress: globalProgress, - state: "", - id: "", - results: undefined, - userId: 0 + state: "", // Vous pouvez mettre à jour ceci si nécessaire + id: "", // Mettez l'ID de la queue ici si nécessaire + results: undefined, // Mettez à jour si vous avez des résultats à afficher + userId: currentUserId || 0 }} colors={{ background: 'text-gray-300', progress: Colors.primary }} /> diff --git a/src/root/SideBar.tsx b/src/root/SideBar.tsx index c791621..04157a7 100644 --- a/src/root/SideBar.tsx +++ b/src/root/SideBar.tsx @@ -20,105 +20,84 @@ const SideBar = ({ onLogout, openItem, setOpenItem }: SideBarProps) => { const itemPath = typeof item === "string" ? item : item.path; navigate(itemPath); }; + const handleDropDown = (title: string) => { setOpenItem(openItem === title ? null : title); }; - const adminItems = [ - { - title: "General", - path: "/administration/general", - isActive: location.pathname === "/administration/general", - }, - { - title: "Modalities", - path: "/administration/modalities", - isActive: location.pathname === "/administration/modalities", - }, - { - title: "Peers", - path: "/administration/peers", - isActive: location.pathname === "/administration/peers", - }, - { - title: "Queues", - path: "/administration/queues/retrieve", - isActive: location.pathname === "/administration/queues", - }, - { - title: "Jobs", - path: "/administration/jobs", - isActive: location.pathname === "/administration/jobs", - }, - { - title: "Users", - path: "/administration/users/users", - isActive: location.pathname === "/administration/users/crud", - }, - { - title: "Labels", - path: "/administration/labels", - isActive: location.pathname === "/administration/labels", - }, + const adminItems = [ + { title: "General", path: "/administration/general", isActive: location.pathname === "/administration/general" }, + { title: "Modalities", path: "/administration/modalities", isActive: location.pathname === "/administration/modalities" }, + { title: "Peers", path: "/administration/peers", isActive: location.pathname === "/administration/peers" }, + { title: "Queues", path: "/administration/queues/retrieve", isActive: location.pathname === "/administration/queues" }, + { title: "Jobs", path: "/administration/jobs", isActive: location.pathname === "/administration/jobs" }, + { title: "Users", path: "/administration/users/users", isActive: location.pathname === "/administration/users/crud" }, + { title: "Labels", path: "/administration/labels", isActive: location.pathname === "/administration/labels" }, ]; return ( ); }; + export default SideBar; diff --git a/src/ui/SelectInput.tsx b/src/ui/SelectInput.tsx index d90a6d9..296954b 100644 --- a/src/ui/SelectInput.tsx +++ b/src/ui/SelectInput.tsx @@ -22,7 +22,7 @@ interface SelectInputProps { const customClass: ClassNamesConfig = { control: (state) => { const borderRadius = state.selectProps.rounded ? 'rounded-3xl' : 'rounded-xl'; - return `border border-gray-300 min-h-[48px] bg-white ${borderRadius} focus:border-primary hover:border-blue-500`; + return `border border-gray-300 min-h-[48px] bg-white ${borderRadius} focus:border-primary hover:border-primary`; }, menu: (state) => { return 'rounded-3xl p-2 bg-white'; diff --git a/src/ui/ToggleEye.tsx b/src/ui/ToggleEye.tsx index 0a6c173..31dbf5b 100644 --- a/src/ui/ToggleEye.tsx +++ b/src/ui/ToggleEye.tsx @@ -4,15 +4,16 @@ import { Eye, EyeSlash } from '../icons'; interface ToggleEyeProps { onToggle: (isVisible: boolean) => void; + className?: string; } const ToggleEye = ({ onToggle }: ToggleEyeProps) => { const [isVisible, setIsVisible] = useState(false); const toggleVisibility = (e: MouseEvent) => { - e.preventDefault(); + e.preventDefault(); setIsVisible(!isVisible); - if(onToggle) onToggle(!isVisible); + if (onToggle) onToggle(!isVisible); }; const iconStyle = { color: Colors.gray }; diff --git a/src/ui/table/FilterTable.tsx b/src/ui/table/FilterTable.tsx index 6ad68ad..cf036ab 100644 --- a/src/ui/table/FilterTable.tsx +++ b/src/ui/table/FilterTable.tsx @@ -36,7 +36,7 @@ const FilterTable = ({ ]) } placeholder={`Min`} - className="w-3/4 h-6 pl-1 border rounded-lg" + className="w-1/2 h-5 pl-1 text-xs border border-gray-300 rounded-2xl" />
) : ( column.setFilterValue(e.target.value)} - placeholder="Search..." - className="w-full pl-2 -mt-2 text-sm font-medium border rounded-lg" - /> -); + type="text" + value={(columnFilterValue ?? "") as string} + onClick={stopPropagation} + onChange={(e) => column.setFilterValue(e.target.value)} + placeholder="Search..." + className="w-2/3 h-5 pl-1 text-xs border border-gray-300 rounded-2xl" + /> + ); }; + export default FilterTable; \ No newline at end of file diff --git a/src/ui/table/Table.tsx b/src/ui/table/Table.tsx index cbea7aa..4c8743c 100644 --- a/src/ui/table/Table.tsx +++ b/src/ui/table/Table.tsx @@ -38,8 +38,8 @@ type TableProps = { onRowClick?: (row: TData) => void; getRowStyles?: (row: TData) => React.CSSProperties | undefined; getRowClasses?: (row: TData) => string | undefined; - onCellEdit?: (rowIndex: string | number, columnId: any, value: any) => void - getRowId?: (originalRow: TData, index: number) => string + onCellEdit?: (rowIndex: string | number, columnId: any, value: any) => void; + getRowId?: (originalRow: TData, index: number) => string; }; function Table({ @@ -149,7 +149,7 @@ function Table({ }); const textXXS = "text-[0.491rem]"; - const headerClass = `bg-${headerColor}`; + const headerClass = `bg-${headerColor} border-b border-gray-200`; const headerText = headerTextSize === "xxs" ? `${textXXS}` : `text-${headerTextSize}`; const firstColumnClass = `sticky left-0 ${headerClass}`; const lastColumnClass = `sticky right-0 bg-white`; @@ -163,7 +163,7 @@ function Table({ return (
- + {table.getHeaderGroups().map(headerGroup => ( @@ -234,16 +234,15 @@ function Table({
-
- {data.length > 0 && table ? ( -
- ) : null} +
+
); } -export default Table; \ No newline at end of file +export default Table; From c4471856967e439e6331cfe59ecfc23cb98074c2 Mon Sep 17 00:00:00 2001 From: Sophie Labyt Date: Wed, 23 Oct 2024 16:02:06 +0200 Subject: [PATCH 4/6] correction style --- src/admin/general/OrthancCard.tsx | 13 +- src/admin/general/RedisCard.tsx | 12 +- src/admin/jobs/JobTable.tsx | 39 ++- src/admin/labels/LabelInputForm.tsx | 1 + src/admin/labels/LabelRoot.tsx | 30 +- src/admin/labels/LabelTable.tsx | 6 +- src/admin/modalities/ModalitiesRoot.tsx | 2 +- src/admin/modalities/ModalitiesTable.tsx | 2 +- src/admin/peers/PeersTable.tsx | 21 +- src/admin/users/oauth/Oauth.tsx | 2 +- src/admin/users/roles/Roles.tsx | 41 +-- src/admin/users/user/Users.tsx | 2 +- src/admin/users/user/UsersTable.tsx | 40 +-- src/datasets/DatasetRoot.tsx | 2 +- src/icons/Wifi.tsx | 10 +- src/import/create/CreateRoot.tsx | 6 +- src/ui/Card.tsx | 4 +- src/ui/Popover.tsx | 4 +- src/ui/table/FilterTable.tsx | 15 +- src/ui/table/Table.tsx | 428 +++++++++++------------ 20 files changed, 332 insertions(+), 348 deletions(-) diff --git a/src/admin/general/OrthancCard.tsx b/src/admin/general/OrthancCard.tsx index 0ac7e32..11c756f 100644 --- a/src/admin/general/OrthancCard.tsx +++ b/src/admin/general/OrthancCard.tsx @@ -83,9 +83,9 @@ const OrthancSettingsCard = ({ orthancData }: OrthancCardProps) => {
setShow(visible)} />
@@ -93,9 +93,9 @@ const OrthancSettingsCard = ({ orthancData }: OrthancCardProps) => { }, header: 'Password' }, - + ]; - + const handleSelectChange = (selectedOption: any) => { mutateVerbosity({ level: selectedOption.value }); @@ -123,10 +123,9 @@ const OrthancSettingsCard = ({ orthancData }: OrthancCardProps) => { className="justify-center bg-gray-100" headerTextSize='xs' headerColor={Colors.white} - /> -
+ />
+ className="flex justify-center gap-3 border-t-2 shadow-inner border-slate-200 bg-light"> @@ -165,7 +157,7 @@ const AnonymizeRoot = () => {
-
+
{
- +
{ className="w-full sm:w-auto" />
-
- +
+ +
+ ); }; diff --git a/src/root/Dashboard/CardAnon.tsx b/src/root/Dashboard/CardAnon.tsx index 1da0ef6..fb7b135 100644 --- a/src/root/Dashboard/CardAnon.tsx +++ b/src/root/Dashboard/CardAnon.tsx @@ -69,7 +69,7 @@ const CardAnon = () => { From b4ce2ec0c8db6390fa69422d4dc45d28a29775fb Mon Sep 17 00:00:00 2001 From: Sophie Labyt Date: Thu, 24 Oct 2024 18:04:01 +0200 Subject: [PATCH 6/6] modify dropdown header --- src/admin/general/RedisCard.tsx | 8 +- src/admin/jobs/JobTable.tsx | 2 +- src/admin/labels/LabelInputForm.tsx | 2 +- src/delete/DeleteRoot.tsx | 2 +- src/queue/ProgressQueueCircle.tsx | 6 +- src/root/Dashboard/CardAnon.tsx | 18 ++-- src/root/Dashboard/Dashboard.tsx | 2 - src/root/Header.tsx | 141 +++++++++++----------------- src/ui/Badge.tsx | 8 +- src/ui/Popover.tsx | 4 +- src/ui/ProgressCircle.tsx | 18 ++-- src/ui/menu/DropDown.tsx | 105 +++++++++++---------- src/ui/table/Table.tsx | 5 +- 13 files changed, 143 insertions(+), 178 deletions(-) diff --git a/src/admin/general/RedisCard.tsx b/src/admin/general/RedisCard.tsx index 6fc56d7..c48f2ea 100644 --- a/src/admin/general/RedisCard.tsx +++ b/src/admin/general/RedisCard.tsx @@ -17,15 +17,15 @@ const RedisCard: React.FC = ({ redisData }) => { const columns: ColumnDef[] = [ { accessorKey: 'address', - header: () =>
Address
, // Default left alignment + header: () =>
Address
, cell: ({ getValue }) => ( -
{getValue() as string}
// Default left alignment +
{getValue() as string}
), }, { accessorKey: 'port', - header: () =>
Port
, // Default left alignment - cell: ({ getValue }) => , // Badge component for port + header: () =>
Port
, + cell: ({ getValue }) => , }, ]; diff --git a/src/admin/jobs/JobTable.tsx b/src/admin/jobs/JobTable.tsx index 0a47943..cedbf56 100644 --- a/src/admin/jobs/JobTable.tsx +++ b/src/admin/jobs/JobTable.tsx @@ -82,7 +82,7 @@ const JobTable = ({ data = [], onJobAction }: JobTableProps) => { className="bg-gray-100" enableColumnFilters enableSorting - getRowClasses={() => "hover:bg-indigo-100 cursor-pointer"} // Ajout de l'effet hover ici + getRowClasses={() => "hover:bg-indigo-100 cursor-pointer"} /> ); }; diff --git a/src/admin/labels/LabelInputForm.tsx b/src/admin/labels/LabelInputForm.tsx index 8d2cd98..f5665dc 100644 --- a/src/admin/labels/LabelInputForm.tsx +++ b/src/admin/labels/LabelInputForm.tsx @@ -31,7 +31,7 @@ const LabelInputForm = function ({ onCreateLabel }: LabelInputFormProps) { onChange={handleInputChange} placeholder="Add new label" bordered - className="w-full border border-gray-300 rounded-r-none shadow-md rounded-l-xl focus:outline-none focus:ring-2 focus:ring-gray-300" // Ajout de l'ombre ici + className="w-full border border-gray-300 rounded-r-none shadow-md rounded-l-xl focus:outline-none focus:ring-2 focus:ring-gray-300" />