diff --git a/src/admin/general/OrthancCard.tsx b/src/admin/general/OrthancCard.tsx index 70a27e9..11c756f 100644 --- a/src/admin/general/OrthancCard.tsx +++ b/src/admin/general/OrthancCard.tsx @@ -80,19 +80,23 @@ 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,13 +120,12 @@ const OrthancSettingsCard = ({ orthancData }: OrthancCardProps) => { - + /> + className="flex justify-center gap-3 border-t-2 shadow-inner border-slate-200 bg-light">
; + return ( +
"hover:bg-indigo-100 cursor-pointer"} + /> + ); }; export default JobTable; diff --git a/src/admin/labels/LabelInputForm.tsx b/src/admin/labels/LabelInputForm.tsx index 571a712..f5665dc 100644 --- a/src/admin/labels/LabelInputForm.tsx +++ b/src/admin/labels/LabelInputForm.tsx @@ -5,6 +5,7 @@ import { Label } from "../../icons"; type LabelInputFormProps = { onCreateLabel: (label: string) => void; + className?: string; }; const LabelInputForm = function ({ onCreateLabel }: LabelInputFormProps) { @@ -30,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" />
); }; diff --git a/src/admin/peers/PeersTable.tsx b/src/admin/peers/PeersTable.tsx index 585564f..062458f 100644 --- a/src/admin/peers/PeersTable.tsx +++ b/src/admin/peers/PeersTable.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 { Peer } from '../../utils/types'; @@ -32,7 +30,7 @@ const PeersTable: React.FC = ({ peerData, onDeletePeer, onEchoP header: 'Actions', id: 'actions', cell: ({ row }) => ( -
+
{/* Alignement à gauche */} @@ -44,14 +42,15 @@ const PeersTable: React.FC = ({ peerData, onDeletePeer, onEchoP } ]; - return
; + return ( +
+ ); }; export default PeersTable; diff --git a/src/admin/users/oauth/Oauth.tsx b/src/admin/users/oauth/Oauth.tsx index ca43332..a452d16 100644 --- a/src/admin/users/oauth/Oauth.tsx +++ b/src/admin/users/oauth/Oauth.tsx @@ -54,7 +54,7 @@ const Oauth = () => { data={oauth2Config || []} onDelete={deleteOauthHandler} /> + className="border-t-2 rounded-b-lg shadow-inner bg-light border-slate-200">
{!showOauthForm ? ( - )} -
+ +
+ {showCreateRoleForm && ( + setShowCreateRoleForm(false)} + /> + )} + {!showCreateRoleForm && ( + + )} +
); diff --git a/src/admin/users/user/Users.tsx b/src/admin/users/user/Users.tsx index 9894cd4..18ad101 100644 --- a/src/admin/users/user/Users.tsx +++ b/src/admin/users/user/Users.tsx @@ -78,7 +78,7 @@ const Users = ({ className = "" }: UsersProps) => { /> ) : null} - +
{!isCreatingUser ? (
"hover:bg-blue-100 cursor-pointer"} /> ); -} +}; export default UsersTable; diff --git a/src/anonymize/AnonQueues.tsx b/src/anonymize/AnonQueues.tsx index c818994..5175571 100644 --- a/src/anonymize/AnonQueues.tsx +++ b/src/anonymize/AnonQueues.tsx @@ -6,14 +6,15 @@ 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 = { - 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,41 @@ 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, + } + ); + + 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, } ); @@ -38,37 +66,39 @@ const AnonQueues = ({ showResults }: 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) => { - return accumulator + currentValue - }, 0); - return (sumProgress / data.length) * 100 - }, [data]) + // If no queue, nothing to display + if (!firstQueue) return null; - //If no queue, nothing to show - if (!firstQueue) return null - //if awaiting value show spinner + // If pending, display the spinner if (isPending || isFetching) return ; return (
-
- +
+ {circle ? ( + + ) : ( + + )}
- { - showResults && ( - - ) - } + {showResults && }
); }; diff --git a/src/anonymize/AnonymizeRoot.tsx b/src/anonymize/AnonymizeRoot.tsx index 8f4135f..f26e8ae 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( @@ -38,13 +35,12 @@ const AnonymizeRoot = () => { [['queue', 'anonymize']], { onSuccess: (jobId) => { - setAnonJobId(jobId) + setAnonJobId(jobId); }, } - ) + ); const patients = useMemo(() => Object.values(anonList.patients), [anonList]); - const studies = useMemo(() => { if (!selectedPatientId) return []; return Object.values(anonList.studies).filter( @@ -74,93 +70,94 @@ const AnonymizeRoot = () => { }); }; - const onChangeStudy = (studyId, key, value) => { - dispatch( - updateAnonymizeStudyValue({ studyId, [key]: value }) - ) - } - - const onRemoveStudy = (studyId) => { - dispatch(removeStudyFromAnonymizeList({ studyId })) - } + const onChangeStudy = (studyId: string, key: string, value: string) => { + dispatch(updateAnonymizeStudyValue({ studyId, [key]: value })); + }; - const onChangePatient = (patientId, key, value) => { - dispatch( - updateAnonymizePatientValue({ patientId, [key]: value }) - ) - } + const onRemoveStudy = (studyId: string) => { + dispatch(removeStudyFromAnonymizeList({ studyId })); + }; - const onRemovePatient = (patientId) => { - studies.filter((study) => study.originalStudy.parentPatient === patientId). - forEach((study) => { - dispatch( - removeStudyFromAnonymizeList({ studyId: study.originalStudy.id }) - ) - } - ) + const onChangePatient = (patientId: string, key: string, value: string) => { + dispatch(updateAnonymizePatientValue({ patientId, [key]: value })); + }; - } + const onRemovePatient = (patientId: string) => { + studies + .filter((study) => study.originalStudy.parentPatient === patientId) + .forEach((study) => { + dispatch(removeStudyFromAnonymizeList({ studyId: study.originalStudy.id })); + }); + }; - const onChangeProfile = (option) => { - dispatch(updateAnonymizationProfile({ anonymizationProfile: option.value })) - } + const onChangeProfile = (option: { value: string }) => { + dispatch(updateAnonymizationProfile({ anonymizationProfile: option.value })); + }; const handleAnonymizeStart = () => { - const anonItems: AnonItem[] = Object.values(anonList.studies).map((study) => { - return { - OrthancStudyID: study.originalStudy.id, - Profile: anonList.anonymizationProfile, - NewPatientID: study.newPatientId, - NewPatientName: study.newPatientName, - NewStudyDescription: study.newStudyDescription, - NewAccessionNumber: study.newAccessionNumber - } - - }) - mutateCreateAnonymizeQueue({ anonItems }) - } + const anonItems: AnonItem[] = Object.values(anonList.studies).map((study) => ({ + OrthancStudyID: study.originalStudy.id, + Profile: anonList.anonymizationProfile, + NewPatientID: study.newPatientId, + NewPatientName: study.newPatientName, + NewStudyDescription: study.newStudyDescription, + NewAccessionNumber: study.newAccessionNumber, + })); + mutateCreateAnonymizeQueue({ anonItems }); + }; return ( - + <> -
-
- Anonymize resources
-
- - - - - +
+
+ Anonymize resources +
+
+ + + Auto Fill +
+ } + className="mr-4" + > +
+ +
+ + 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/datasets/DatasetRoot.tsx b/src/datasets/DatasetRoot.tsx index 20db04e..28597b7 100644 --- a/src/datasets/DatasetRoot.tsx +++ b/src/datasets/DatasetRoot.tsx @@ -112,7 +112,7 @@ const DatasetRoot = () => { - + diff --git a/src/delete/DeleteRoot.tsx b/src/delete/DeleteRoot.tsx index 8e7d4f7..96e1ad5 100644 --- a/src/delete/DeleteRoot.tsx +++ b/src/delete/DeleteRoot.tsx @@ -24,6 +24,7 @@ const DeleteRoot = () => { [[]], { onSuccess: (uuid) => { + console.log("Queue created with UUID:", uuid); setQueueUuid(uuid); }, } 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/icons/Wifi.tsx b/src/icons/Wifi.tsx index f8d41f3..57253fe 100644 --- a/src/icons/Wifi.tsx +++ b/src/icons/Wifi.tsx @@ -1,9 +1,11 @@ -import { BiWifi } from "react-icons/bi"; +import { FaWifi } from "react-icons/fa"; const Wifi = (props) => { return ( - - ) + <> + + + ); } -export default Wifi \ No newline at end of file +export default Wifi; diff --git a/src/import/create/CreateRoot.tsx b/src/import/create/CreateRoot.tsx index 9817ee0..195bffe 100644 --- a/src/import/create/CreateRoot.tsx +++ b/src/import/create/CreateRoot.tsx @@ -61,11 +61,11 @@ const CreateRoot: React.FC = () => { return ( <> -
+
-
+
{ />
-
+
- - - - {/* 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..6f89fdd --- /dev/null +++ b/src/root/Dashboard/CardAnon.tsx @@ -0,0 +1,75 @@ +import Button from "../../ui/Button"; +import Card, { CardHeader, CardBody, CardFooter } from "../../ui/Card"; +import { Colors } from "../../utils/enums"; +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, + } + ); + + 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]); + + useCustomQuery(['queue', 'anonymize', firstQueue], () => getAnonymizeQueue(firstQueue), { + refetchInterval: globalProgress < 100 ? 2000 : false, + enabled: !!firstQueue, + }); + + // If no queue is found, display nothing + if (!firstQueue) return null; + // If data is loading, display a spinner + if (isPending || isFetching) return ; + + return ( + + + + { }} + queueData={{ + progress: globalProgress, + state: "", + id: "", + results: undefined, + userId: currentUserId || 0 + }} + colors={{ background: 'text-gray-300', progress: Colors.primary }} + /> + + + + + + ); +}; + +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..200fcba --- /dev/null +++ b/src/root/Dashboard/Dashboard.tsx @@ -0,0 +1,36 @@ +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/Header.tsx b/src/root/Header.tsx index 6a9973f..bc1334c 100644 --- a/src/root/Header.tsx +++ b/src/root/Header.tsx @@ -8,10 +8,12 @@ import ToggleSwitch from '../ui/menu/ToggleSwitch'; import BannerItems from '../ui/menu/BannerItems'; import DeleteList from './ToolList'; import { Gear, Language, Notification, User } from '../icons'; +import { ToogleChevron } from '../ui'; type Item = { title: string; - path: string; + path?: string; + code?: string; isActive: boolean; }; @@ -25,17 +27,11 @@ const Header: React.FC = ({ title }) => { const { i18n } = useTranslation(); const [openItem, setOpenItem] = useState(null); - const dropdownRefLanguage = useRef(null); - const dropdownRefSettingsUser = useRef(null); + const dropdownRef = useRef(null); useEffect(() => { const handleOutsideClick = (event: MouseEvent) => { - if ( - dropdownRefLanguage.current && - !dropdownRefLanguage.current.contains(event.target as Node) && - dropdownRefSettingsUser.current && - !dropdownRefSettingsUser.current.contains(event.target as Node) - ) { + if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { setOpenItem(null); } }; @@ -51,39 +47,20 @@ const Header: React.FC = ({ title }) => { }; }, [openItem]); - const handleDropDown = (name: string) => { - setOpenItem(prev => (prev === name ? null : name)); + const handleDropDown = () => { + setOpenItem(prev => (prev ? null : 'Dropdown')); }; - const handleSettingsItemClick = (item: Item) => { - navigate(item.path); - setOpenItem(null); - }; - - const handleLeftIconClick = () => { - if (location.pathname !== '/') { - navigate('/'); + const handleItemClick = (item: Item) => { + if (item.path) { + navigate(item.path); + } else if (item.code) { + i18n.changeLanguage(item.code); } + setOpenItem(null); }; - const isOpen = (item: string): boolean => openItem === item; - - const ItemsLanguage = [ - { - title: 'English', - code: 'en', - path: '/english', - isActive: location.pathname === '/english', - }, - { - title: 'Français', - code: 'fr', - path: '/francais', - isActive: location.pathname === '/francais', - }, - ]; - - const ItemsSettingsUser: Item[] = [ + const Items: Item[] = [ { title: 'Profile', path: '/profile', @@ -94,78 +71,66 @@ const Header: React.FC = ({ title }) => { path: '/settings', isActive: location.pathname === '/settings', }, + { + title: 'English', + code: 'en', + isActive: i18n.language === 'en', + }, + { + title: 'Français', + code: 'fr', + isActive: i18n.language === 'fr', + }, ]; return ( navigate('/')} className="sticky top-0 z-50 bg-white" >
handleDropDown('Language')} + ref={dropdownRef} + className="relative flex flex-col w-80" + isOpen={openItem === 'Dropdown'} + dropDownOpen={handleDropDown} dropDown={ -
+
{ - i18n.changeLanguage(item.code); - setOpenItem(null); - }} - isOpen={isOpen('Language')} - className="w-40" + elements={Items} + onSelect={(item) => handleItemClick(item)} + isOpen={openItem === 'Dropdown'} setOpenItem={setOpenItem} + className="w-80" />
} - > - handleDropDown('Language')}> - - - {ItemsLanguage.find((item) => item.code === i18n.language)?.title} - - - - handleDropDown('SettingsUser')} - dropDown={ -
- handleSettingsItemClick(item)} - isOpen={isOpen('SettingsUser')} - setOpenItem={setOpenItem} - className="w-56" - /> -
- } - > - { console.log('Toggle state:', isChecked); }} /> - - - +
+ + + + {Items.find((item) => item.code === i18n.language)?.title} + + + + + + +
+ +
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/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/Badge.tsx b/src/ui/Badge.tsx index 373ddd1..ed958f2 100644 --- a/src/ui/Badge.tsx +++ b/src/ui/Badge.tsx @@ -18,10 +18,10 @@ const Badge = ({ }: BadgeProps) => { const sizeClasses = { - sm: "text-xs p-1.5", // Réduction légère du texte - md: "text-sm p-2 px-3", // Taille par défaut pour md - lg: "text-base p-3", // Taille par défaut pour lg - xl: "text-lg p-4", // Taille par défaut pour xl + sm: "text-xs p-1.5", + md: "text-sm p-2 px-3", + lg: "text-base p-3", + xl: "text-lg p-4" }; const variants = { diff --git a/src/ui/Card.tsx b/src/ui/Card.tsx index 4261d33..2202c58 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]: '' }; @@ -93,7 +93,7 @@ const CardBody = ({ return (
{children}
@@ -105,7 +105,7 @@ const CardFooter = ({ children, className = "", color }: CardFooterProps) => { const footerClass = getColorClass(color); return ( -
+
{children ||
}
); diff --git a/src/ui/ProgressCircle.tsx b/src/ui/ProgressCircle.tsx index 2ecb3b6..5119482 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; + children?: React.ReactNode; + className?: string; + progressColor?: string; }; -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 = '', + progressColor = 'text-orange-600', +}: ProgressCircleProps) => { + const radius = 15; + 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; 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/menu/DropDown.tsx b/src/ui/menu/DropDown.tsx index 572e3c4..14013d4 100644 --- a/src/ui/menu/DropDown.tsx +++ b/src/ui/menu/DropDown.tsx @@ -10,58 +10,67 @@ type DropDownProps = { dropDown: React.ReactNode | boolean; }; -const DropDown = forwardRef(({ chevronPosition, children, className, isOpen: isOpenProp, dropDownOpen, dropDown }, ref) => { - const [isOpenState, setIsOpenState] = useState(false); +const DropDown = forwardRef( + ({ chevronPosition, children, className, isOpen: isOpenProp, dropDownOpen, dropDown }, ref) => { + const [isOpenState, setIsOpenState] = useState(false); - const isOpenUse = (isOpenProp !== undefined && dropDownOpen !== undefined); - const isOpen = isOpenProp !== undefined ? isOpenProp : isOpenState; + const isOpenUse = (isOpenProp !== undefined && dropDownOpen !== undefined); + const isOpen = isOpenProp !== undefined ? isOpenProp : isOpenState; - const handleClick = (event: React.MouseEvent) => { - event.stopPropagation(); - isOpenUse ? dropDownOpen && dropDownOpen() : setIsOpenState(!isOpenState); - }; + const handleClick = (event: React.MouseEvent) => { + event.stopPropagation(); + isOpenUse ? dropDownOpen && dropDownOpen() : setIsOpenState(!isOpenState); + }; - const handleBlur = (event: React.FocusEvent) => { - event.stopPropagation(); - isOpenUse ? dropDownOpen && dropDownOpen() : setIsOpenState(false); - }; + const handleBlur = (event: React.FocusEvent) => { + event.stopPropagation(); + isOpenUse ? dropDownOpen && dropDownOpen() : setIsOpenState(false); + }; - return ( -
-
- {children} - {typeof dropDown === 'boolean' && dropDown && ( -
-
    -
  • - - -
  • -
  • - - -
  • -
  • - - -
  • -
-
- )} - {typeof dropDown !== 'boolean' && dropDown} - {chevronPosition && - - } + return ( +
+
+ {children} + + {typeof dropDown === 'boolean' && dropDown && ( +
+
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
+
+ )} + + {typeof dropDown !== 'boolean' && dropDown} + + {chevronPosition && ( + + )} +
-
- ); -}); + ); + } +); export default DropDown; diff --git a/src/ui/table/FilterTable.tsx b/src/ui/table/FilterTable.tsx index 6ad68ad..5a990ef 100644 --- a/src/ui/table/FilterTable.tsx +++ b/src/ui/table/FilterTable.tsx @@ -1,18 +1,14 @@ import { Column } from "@tanstack/react-table"; -/** - * Component that displays the filter of a column. - */ type FilterTableProps = { column: Column; table: any; }; - const FilterTable = ({ column, table, -}:FilterTableProps) => { +}: FilterTableProps) => { const firstValue = table .getPreFilteredRowModel() .flatRows[0]?.getValue(column.id); @@ -23,9 +19,8 @@ const FilterTable = ({ e.stopPropagation(); }; - return typeof firstValue === "number" ? ( -
+
) : ( 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 font-medium text-gray-600 border border-gray-300 rounded-2xl placeholder:text-gray-400" + /> + ); }; + 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..f2e6e85 100644 --- a/src/ui/table/Table.tsx +++ b/src/ui/table/Table.tsx @@ -1,16 +1,15 @@ import React, { useEffect, useState } from 'react'; import { - useReactTable, - getCoreRowModel, - getSortedRowModel, - flexRender, - ColumnDef, - SortingState, - ColumnFiltersState, - getFilteredRowModel, - getPaginationRowModel, + useReactTable, + getCoreRowModel, + getSortedRowModel, + flexRender, + ColumnDef, + SortingState, + ColumnFiltersState, + getFilteredRowModel, + getPaginationRowModel, } from '@tanstack/react-table'; - import { Colors } from "../../utils/enums"; import FilterTable from './FilterTable'; import Footer from '../table/Footer'; @@ -20,230 +19,215 @@ import EditableCell from './EditableCell'; export type textSize = "xxs" | "xs" | "sm" | "base" | "lg"; type TableProps = { - data?: TData[]; - id?: string; - columns: ColumnDef[]; - enableSorting?: boolean; - enableColumnFilters?: boolean; - headerColor?: Colors; - headerTextSize?: textSize; - className?: string; - pageSize?: number; - pinFirstColumn?: boolean; - pinLastColumn?: boolean; - enableRowSelection?: boolean; - selectedRow?: Record; - columnVisibility?: Record; - onRowSelectionChange?: (selectedState: Record) => void; - 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 + data?: TData[]; + id?: string; + columns: ColumnDef[]; + enableSorting?: boolean; + enableColumnFilters?: boolean; + headerColor?: Colors; + headerTextSize?: textSize; + className?: string; + pageSize?: number; + pinFirstColumn?: boolean; + pinLastColumn?: boolean; + enableRowSelection?: boolean; + selectedRow?: Record; + columnVisibility?: Record; + onRowSelectionChange?: (selectedState: Record) => void; + 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; }; function Table({ - data = [], - columns, - id = 'id', - enableSorting = false, - enableColumnFilters = false, - headerColor = Colors.white, - className, - pageSize = 10, - headerTextSize = "sm", - pinFirstColumn = false, - pinLastColumn = false, - enableRowSelection = false, - selectedRow = {}, - columnVisibility = {}, - onRowSelectionChange = (selectedState: Record) => { return null }, - onRowClick, - getRowStyles, - getRowClasses = (row) => 'bg-indigo-50', - onCellEdit = () => { }, - getRowId = (originalRow, index) => { return originalRow?.[id] ?? index } + data = [], + columns, + id = 'id', + enableSorting = false, + enableColumnFilters = false, + headerColor = Colors.white, + className, + pageSize = 10, + headerTextSize = "sm", + pinFirstColumn = false, + pinLastColumn = false, + enableRowSelection = false, + selectedRow = {}, + columnVisibility = {}, + onRowSelectionChange = (selectedState: Record) => { return null }, + onRowClick, + getRowStyles, + getRowClasses = (row) => 'bg-indigo-50', + onCellEdit = () => { }, + getRowId = (originalRow, index) => { return originalRow?.[id] ?? index }, }: TableProps) { - const [sorting, setSorting] = useState([]); - const [columnFilters, setColumnFilters] = useState([]); - const [rowSelection, setRowSelection] = useState(selectedRow) - const [pagination, setPagination] = useState({ - pageIndex: 0, - pageSize: pageSize, - }); + const [sorting, setSorting] = useState([]); + const [columnFilters, setColumnFilters] = useState([]); + const [rowSelection, setRowSelection] = useState(selectedRow); + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: pageSize, + }); + + useEffect(() => { + onRowSelectionChange(rowSelection); + }, [JSON.stringify(rowSelection)]); + + const handlePageSizeChange = (newPageSize: number) => { + setPagination((prev) => ({ + ...prev, + pageSize: newPageSize, + })); + }; - useEffect(() => { - onRowSelectionChange(rowSelection) - }, [JSON.stringify(rowSelection)]) + const selectionColumn: ColumnDef = { + id: 'selection', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + e.stopPropagation()} + /> + ), + }; - const handlePageSizeChange = (newPageSize: number) => { - setPagination((prev) => ({ - ...prev, - pageSize: newPageSize, - })); - }; + const tableColumns = enableRowSelection ? [selectionColumn, ...columns] : columns; - const selectionColumn: ColumnDef = { - id: 'selection', - header: ({ table }) => ( - - ), - cell: ({ row }) => ( - e.stopPropagation()} - /> - ), - }; + const table = useReactTable({ + data, + columns: tableColumns, + defaultColumn: { + cell: EditableCell, + }, + state: { + sorting, + columnFilters, + pagination, + rowSelection: selectedRow, + columnVisibility: columnVisibility + }, + getRowId: getRowId, + onPaginationChange: setPagination, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onRowSelectionChange: setRowSelection, + getPaginationRowModel: getPaginationRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + enableRowSelection, + enableColumnFilters, + enableSorting, + meta: { + getRowStyles: (row: any) => { + const styles = getRowStyles ? getRowStyles(row) : undefined; + return styles; + }, + getRowClasses: (row: any) => { + const classes = getRowClasses ? getRowClasses(row) : undefined; + return classes; + }, + updateData: ( + rowIndex: string | number, + columnId: any, + value: any + ) => { + onCellEdit(rowIndex, columnId, value); + } + } + }); - const tableColumns = enableRowSelection ? [selectionColumn, ...columns] : columns; + const textXXS = "text-[0.491rem]"; + const headerText = headerTextSize === "xxs" ? textXXS : `text-${headerTextSize}`; - const table = useReactTable({ - data, - columns: tableColumns, - defaultColumn: { - cell: EditableCell, - }, - state: { - sorting, - columnFilters, - pagination, - rowSelection: selectedRow, - columnVisibility: columnVisibility - }, - getRowId: getRowId, - onPaginationChange: setPagination, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - onRowSelectionChange: setRowSelection, - getPaginationRowModel: getPaginationRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - enableRowSelection, - enableColumnFilters, - enableSorting, - meta: { - getRowStyles: (row: any) => { - const styles = getRowStyles ? getRowStyles(row) : undefined; - return styles; - }, - getRowClasses: (row: any) => { - const classes = getRowClasses ? getRowClasses(row) : undefined; - return classes; - }, - updateData: ( - rowIndex: string | number, - columnId: any, - value: any - ) => { - onCellEdit(rowIndex, columnId, value) - } - } - }); + const firstColumnClass = `sticky left-0 bg-${headerColor} border-b border-gray-300 text-center`; + const lastColumnClass = "sticky right-0 bg-white"; - const textXXS = "text-[0.491rem]"; - const headerClass = `bg-${headerColor}`; - const headerText = headerTextSize === "xxs" ? `${textXXS}` : `text-${headerTextSize}`; - const firstColumnClass = `sticky left-0 ${headerClass}`; - const lastColumnClass = `sticky right-0 bg-white`; + const getColumnClasses = (index: number, length: number) => { + if (pinFirstColumn && index === 0) return firstColumnClass; + if (pinLastColumn && index === length - 1) return lastColumnClass; + return ''; + }; - const getColumnClasses = (index: number, length: number) => { - if (pinFirstColumn && index === 0) return firstColumnClass; - if (pinLastColumn && index === length - 1) return lastColumnClass; - return ''; - }; + return ( +
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header, index) => ( + + ))} + + ))} + - return ( -
-
+
+ {/* Left aligned header text */} +
+ {flexRender(header.column.columnDef.header, header.getContext())} +
+ {enableSorting && header.column.getCanSort() && ( + + {header.column.getIsSorted() === 'desc' ? : } + + )} +
+ {/* Render filter directly below the header */} + {header.column.getCanFilter() && ( +
+ +
+ )} +
- - {table.getHeaderGroups().map(headerGroup => ( - - - {headerGroup.headers.map((header, index) => ( - - ))} - - {headerGroup.headers.some(header => header.column.getCanFilter()) && ( - - {headerGroup.headers.map((header, index) => ( - - ))} - - )} - - ))} - - - {table.getRowModel().rows.map((row, rowIndex) => ( - - { - if (onRowClick) { - onRowClick(row.original); - } - }} - > - {row.getVisibleCells().map((cell, cellIndex) => ( - - ))} - - - ))} - + + {table.getRowModel().rows.map((row, rowIndex) => ( + + { + if (onRowClick) { + onRowClick(row.original); + } + }} + > + {row.getVisibleCells().map((cell, cellIndex) => ( + + ))} + + + ))} + +
-
- {flexRender(header.column.columnDef.header, header.getContext())} - {enableSorting && header.column.getCanSort() && ( - - {header.column.getIsSorted() === 'desc' ? ( - - ) : ( - - )} - - )} -
-
- {header.column.getCanFilter() ? ( -
e.stopPropagation()}> - -
- ) : null} -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
- -
- {data.length > 0 && table ? ( -
- - ); +