From 02a1abc93f7e4a4c31304dc3060d04a1cbfc0a0e Mon Sep 17 00:00:00 2001 From: mario Date: Fri, 11 Apr 2025 16:43:12 +0700 Subject: [PATCH] mau coba di hangtuah yang sudah oke fitur exp nya --- .../src/Components/SidePanelWithServices.tsx | 23 +- .../src/components/SidePanel/SidePanel.tsx | 249 ++++++++++++++++++ .../components/StudyBrowser/StudyBrowser.tsx | 1 + .../src/components/StudyItem/StudyItem.tsx | 37 ++- 4 files changed, 297 insertions(+), 13 deletions(-) diff --git a/extensions/default/src/Components/SidePanelWithServices.tsx b/extensions/default/src/Components/SidePanelWithServices.tsx index c4fd32a..2b02164 100644 --- a/extensions/default/src/Components/SidePanelWithServices.tsx +++ b/extensions/default/src/Components/SidePanelWithServices.tsx @@ -71,11 +71,25 @@ const SidePanelWithServices = ({ const activatePanelSubscription = panelService.subscribe( panelService.EVENTS.ACTIVATE_PANEL, (activatePanelEvent: Types.ActivatePanelEvent) => { - if (sidePanelOpen || activatePanelEvent.forceActive) { - const tabIndex = tabs.findIndex(tab => tab.id === activatePanelEvent.panelId); - if (tabIndex !== -1) { - setActiveTabIndex(tabIndex); + // Handle the `-exp` suffix logic + const isExpertisePanel = activatePanelEvent.panelId.endsWith('-exp'); + const realPanelID = isExpertisePanel + ? activatePanelEvent.panelId.replace(/-exp$/, '') + : activatePanelEvent.panelId; + + const tabIndex = tabs.findIndex(tab => tab.id === realPanelID); + + if (isExpertisePanel && side === 'right') { + const shouldOpen = !sidePanelOpen; // Use sidePanelOpen to determine toggle state + setSidePanelOpen(shouldOpen); + + if (shouldOpen) { + setActiveTabIndex(tabIndex !== -1 ? tabIndex : null); + } else { + setActiveTabIndex(null); } + } else if (tabIndex !== -1) { + setActiveTabIndex(tabIndex); } } ); @@ -95,6 +109,7 @@ const SidePanelWithServices = ({ onClose={handleClose} onActiveTabIndexChange={handleActiveTabIndexChange} expandedWidth={expandedWidth} + servicesManager={servicesManager} // Pass servicesManager ke SidePanel /> ); }; diff --git a/platform/ui-next/src/components/SidePanel/SidePanel.tsx b/platform/ui-next/src/components/SidePanel/SidePanel.tsx index feee007..f866190 100644 --- a/platform/ui-next/src/components/SidePanel/SidePanel.tsx +++ b/platform/ui-next/src/components/SidePanel/SidePanel.tsx @@ -4,6 +4,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { Icons } from '../Icons'; import { TooltipTrigger, TooltipContent, TooltipProvider, Tooltip } from '../Tooltip'; import { Separator } from '../Separator'; +import { ScrollArea } from '../ScrollArea'; type StyleMap = { open: { @@ -152,10 +153,22 @@ const SidePanel = ({ onClose, expandedWidth = 280, onActiveTabIndexChange, + servicesManager, // Tambah servicesManager as a prop }) => { const [panelOpen, setPanelOpen] = useState(activeTabIndexProp !== null); const [activeTabIndex, setActiveTabIndex] = useState(0); + const [isExpertiseVisible, setIsExpertiseVisible] = useState(false); // New state for expertise visibility + const [expertiseData, setExpertiseData] = useState(null); + const [isExpertiseLoading, setIsExpertiseLoading] = useState(false); + + const { cornerstoneViewportService } = servicesManager.services; + const [viewportData, setViewportData] = useState(null); + + // Harusnya (viewportId), tapi karena gabutuh perubahan viewport maka dihardcode 'default' + const viewportInfo = cornerstoneViewportService.getViewportInfo('default'); + const studyInstanceUID = viewportInfo?.viewportData?.data?.[0]?.StudyInstanceUID || ''; + const styleMap = createStyleMap(expandedWidth, borderSize, collapsedWidth); const baseStyle = createBaseStyle(expandedWidth); const gridAvailableWidth = expandedWidth - closeIconWidth - gridHorizontalPadding; @@ -196,6 +209,16 @@ const SidePanel = ({ updateActiveTabIndex(activeTabIndexProp); }, [activeTabIndexProp, updateActiveTabIndex]); + const toggleExpertiseVisibility = () => { + const shouldOpenExpertise = !isExpertiseVisible; + setIsExpertiseVisible(shouldOpenExpertise); + + // Open the side panel if the expertise panel is being shown + if (shouldOpenExpertise && !panelOpen) { + updatePanelOpen(true); + } + }; + const getCloseStateComponent = () => { const _childComponents = Array.isArray(tabs) ? tabs : [tabs]; return ( @@ -255,6 +278,230 @@ const SidePanel = ({ ); }; + // Tambahkan di atas useEffect fetchExpertiseData + useEffect(() => { + const fetchAccessionNumber = async () => { + if (!studyInstanceUID) { + console.warn('No StudyInstanceUID available'); + return; + } + + try { + const qidoRootUrl = getQidoRootUrl(); + if (!qidoRootUrl) { + console.warn('QIDO root URL not configured'); + return; + } + + // Fetch data with specific fields including Accession Number + const response = await fetch( + `${qidoRootUrl}/studies?includefield=00080050&StudyInstanceUID=${studyInstanceUID}` + ); + + if (!response.ok) { + throw new Error('Failed to fetch study data'); + } + + const data = await response.json(); + + if (data && data.length > 0) { + // Extract accession number from DICOM tag 00080050 + const accessionNumber = data[0]['00080050']?.Value?.[0] || ''; + + // If we have an accession number, call fetchExpertiseData + if (accessionNumber) { + console.log('Found Accession Number:', accessionNumber); + fetchExpertiseData(accessionNumber); + } else { + console.warn('Accession number not found in study data'); + } + } else { + console.warn('No study data returned'); + } + } catch (error) { + console.error('Error fetching accession number:', error); + } + }; + + // Helper function to get QIDO root URL + const getQidoRootUrl = () => { + const { config } = window; + if (!config?.dataSources || !config.defaultDataSourceName) { + return null; + } + + const dataSource = config.dataSources.find( + ds => ds.sourceName === config.defaultDataSourceName + ); + + return dataSource?.configuration?.qidoRoot; + }; + + fetchAccessionNumber(); + }, [studyInstanceUID]); // Run when studyInstanceUID changes + + // Ubah fungsi fetchExpertiseData menjadi dengan parameter accessionNumber + const fetchExpertiseData = async accessionNumber => { + try { + // Check if window.config.expertise_host exists + if (!window.config?.expertise_host) { + console.warn('Expertise host not configured in window.config.expertise_host'); + return; + } + + if (!accessionNumber) { + console.warn('No accession number available for expertise lookup'); + return; + } + + setIsExpertiseLoading(true); + const url = `${window.config.expertise_host}/nv/query.php?method=view&AccessionNumber=${encodeURIComponent(accessionNumber)}`; + + const response = await fetch(url); + const data = await response.json(); + // console.log('Study data:', data); + + const data = { + study: { + accession_no: 'CR.250411.001', + study_iuid: '1.2.826.0.1.3680043.9.7307.1.20250411001', + study_description: '', + study_datetime: '20250411093937', + number_of_series: '1', + number_of_instances: '1', + modality: 'CR', + patient_mrn: '00000941', + patient_name: 'NEFANNY RIDWAN', + patient_sex: 'F', + patient_date_of_birth: '19881127', + patient_age: '36Y 4M 14D', + expertise: [ + { + expertise: + 'Keterangan : MCU\r\n\r\nRadiografi Thorax PA (inspirasi kurang)\r\n\r\nCor : besar dan bentuk normal\r\nPulmo : tak tampak infiltrat\r\nTrachea tampak di tengah\r\nSinus phrenicocostalis kanan kiri tajam\r\nHemidiafragma kanan kiri tampak baik\r\nTulang-tulang tampak baik\r\nSoft tissue tak tampak kelainan\r\n\r\nKesan :\r\nTidak tampak kelainan signifikan pada pemeriksaan saat ini\r\n\r\nBTK,', + radiologist: 'dr. Hendra Boy Situmorang, Sp.Rad ', + expertise_dttm: '2025-04-11 09:43', + radiologist_edit: null, + expertise_edit_dttm: '0000-00-00 00:00', + ordering_physician: 'dr. Laksmitasari Dewi', + }, + ], + series: [ + { + series_number: '1', + series_iuid: '1.2.156.112536.2.560.28134011043131122.1519098341436.1', + series_description: 'V04_0014', + number_of_instances: 1, + thumbnail: + 'http://192.168.22.3/nv/wado_proxy_thumb.php?requestType=WADO&studyUID=1.2.826.0.1.3680043.9.7307.1.20250411001&seriesUID=1.2.156.112536.2.560.28134011043131122.1519098341436.1&objectUID=1.2.156.112536.2.560.28134011043131122.1519098341436.4&rows=123', + sop_iuids: ['1.2.156.112536.2.560.28134011043131122.1519098341436.4'], + }, + ], + }, + }; + + if (data?.study?.expertise && data.study.expertise.length > 0) { + setExpertiseData(data.study.expertise[0]); + } + } catch (error) { + console.error('Error fetching expertise data:', error); + } finally { + setIsExpertiseLoading(false); + } + }; + + const getExpertisePanel = () => { + if (side !== 'right') return null; // Only show in the right side panel + + if (isExpertiseLoading) { + return ( +
+ Loading expertise data... +
+ ); + } + + if (!expertiseData) { + return null; + } + + const parseExpertise = text => { + if (!text) return {}; + + const result = {}; + let currentSection = 'Keterangan'; + + // Split expertise text by lines and process each line + const lines = text.split('\r\n').filter(line => line.trim() !== ''); + + lines.forEach(line => { + // Check if this is a section header + if (line.includes(':') && !line.trim().startsWith('-')) { + const parts = line.split(':'); + currentSection = parts[0].trim(); + const value = parts[1]?.trim() || ''; + + if (value) { + if (!result[currentSection]) { + result[currentSection] = []; + } + result[currentSection].push(value); + } + } else if (line.toLowerCase().includes('kesan')) { + currentSection = 'Kesan'; + } else { + // Add line to current section + if (!result[currentSection]) { + result[currentSection] = []; + } + result[currentSection].push(line.trim()); + } + }); + + return result; + }; + + const parsedSections = parseExpertise(expertiseData.expertise); + + // Create formatted data structure + const formattedData = [ + { label: 'Dokter Pengirim', value: expertiseData.ordering_physician || '' }, + { label: 'Dokter Radiologis', value: expertiseData.radiologist || '' }, + { label: 'Waktu Expertise', value: expertiseData.expertise_dttm || '' }, + ]; + + // Add additional sections from parsed text + Object.entries(parsedSections).forEach(([key, value]) => { + formattedData.push({ + label: key, + value: Array.isArray(value) ? value : [value], + }); + }); + + return ( + +

Expertise

+ {formattedData.map((section, index) => ( +
+
{section.label}:
+ {Array.isArray(section.value) ? ( +
    + {section.value.map((item, idx) => ( +
  • {item}
  • + ))} +
+ ) : ( +

{section.value}

+ )} +
+ ))} +
+ ); + }; + const getCloseIcon = () => { return (
) : ( {getCloseStateComponent()} @@ -413,6 +661,7 @@ SidePanel.propTypes = { onClose: PropTypes.func, onActiveTabIndexChange: PropTypes.func, expandedWidth: PropTypes.number, + servicesManager: PropTypes.object.isRequired, // Tambah servicesManager prop }; export { SidePanel }; diff --git a/platform/ui-next/src/components/StudyBrowser/StudyBrowser.tsx b/platform/ui-next/src/components/StudyBrowser/StudyBrowser.tsx index 540b80b..ed097e4 100644 --- a/platform/ui-next/src/components/StudyBrowser/StudyBrowser.tsx +++ b/platform/ui-next/src/components/StudyBrowser/StudyBrowser.tsx @@ -62,6 +62,7 @@ const StudyBrowser = ({ data-cy="thumbnail-list" viewPreset={viewPreset} onThumbnailContextMenu={onThumbnailContextMenu} + servicesManager={servicesManager} // Pass servicesManager ke Study Item /> ); diff --git a/platform/ui-next/src/components/StudyItem/StudyItem.tsx b/platform/ui-next/src/components/StudyItem/StudyItem.tsx index 00b5d94..17f51e9 100644 --- a/platform/ui-next/src/components/StudyItem/StudyItem.tsx +++ b/platform/ui-next/src/components/StudyItem/StudyItem.tsx @@ -20,6 +20,7 @@ const StudyItem = ({ onClickUntrack, viewPreset = 'thumbnails', onThumbnailContextMenu, + servicesManager, // Tambah servicesManager as a prop }: withAppTypes) => { return ( {isExpanded && displaySets && ( - + <> + {/* Expertise Button */} +
{ + // Trigger the expertise panel in the right side panel (segmentation Panel) + servicesManager.services.panelService.activatePanel( + '@ohif/extension-cornerstone.panelModule.panelSegmentation-exp', + true + ); + }} + > + View Expertise +
+ + {/* Thumbnails */} + + )} @@ -86,6 +104,7 @@ StudyItem.propTypes = { onDoubleClickThumbnail: PropTypes.func, onClickUntrack: PropTypes.func, viewPreset: PropTypes.string, + servicesManager: PropTypes.object.isRequired, // Tambah servicesManager prop }; export { StudyItem };