Compare commits

...

2 Commits

Author SHA1 Message Date
yashwin-foxy
692a8156da ai prediction detil screen added 2025-08-13 18:21:57 +05:30
yashwin-foxy
413a1d74de added patient detail screen 2025-08-12 18:50:19 +05:30
64 changed files with 4255 additions and 436 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -2,36 +2,36 @@
"migIndex": 1,
"data": [
{
"path": "app/assets/fonts/Roboto-Black.ttf",
"sha1": "d1678489a8d5645f16486ec52d77b651ff0bf327"
"path": "app/assets/fonts/WorkSans-Bold.ttf",
"sha1": "ec84061651ead3c3c5cbb61c2d338aca0bacdc1e"
},
{
"path": "app/assets/fonts/Roboto-Bold.ttf",
"sha1": "508c35dee818addce6cc6d1fb6e42f039da5a7cf"
"path": "app/assets/fonts/WorkSans-ExtraBold.ttf",
"sha1": "0b371d1dbfbdd15db880bbd129b239530c71accb"
},
{
"path": "app/assets/fonts/Roboto-ExtraBold.ttf",
"sha1": "3dbfd71b6fbcfbd8e7ee8a8dd033dc5aaad63249"
"path": "app/assets/fonts/WorkSans-ExtraLight.ttf",
"sha1": "74596e55487e2961b6c43993698d658e2ceee77b"
},
{
"path": "app/assets/fonts/Roboto-ExtraLight.ttf",
"sha1": "df556e64732e5c272349e13cb5f87591a1ae779b"
"path": "app/assets/fonts/WorkSans-Light.ttf",
"sha1": "293e11dae7e8b930bf5eea0b06ca979531f22189"
},
{
"path": "app/assets/fonts/Roboto-Light.ttf",
"sha1": "318b44c0a32848f78bf11d4fbf3355d00647a796"
"path": "app/assets/fonts/WorkSans-Medium.ttf",
"sha1": "c281f8454dd193c2260e43ae2de171c5dd4086e4"
},
{
"path": "app/assets/fonts/Roboto-Medium.ttf",
"sha1": "fa5192203f85ddb667579e1bdf26f12098bb873b"
"path": "app/assets/fonts/WorkSans-Regular.ttf",
"sha1": "5e0183b29b57c54595c62ac6bc223b21f1434226"
},
{
"path": "app/assets/fonts/Roboto-Regular.ttf",
"sha1": "3bff51436aa7eb995d84cfc592cc63e1316bb400"
"path": "app/assets/fonts/WorkSans-SemiBold.ttf",
"sha1": "64b8fe156fafce221a0f66504255257053fc6062"
},
{
"path": "app/assets/fonts/Roboto-SemiBold.ttf",
"sha1": "9ca139684fe902c8310dd82991648376ac9838db"
"path": "app/assets/fonts/WorkSans-Thin.ttf",
"sha1": "a62251331038fdd079c47bc413a350efbf702db8"
}
]
}

View File

@ -0,0 +1,502 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DICOM Viewer</title>
<style>
html, body {
margin: 0;
padding: 0;
height: 100%;
background: rgb(8, 0, 0);
font-family: Arial, sans-serif;
}
#dicomImage {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: white;
}
.loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: white;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #2196F3;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin-bottom: 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
color: #F44336;
text-align: center;
padding: 20px;
}
.success {
color: #4CAF50;
text-align: center;
padding: 20px;
}
.debug {
position: fixed;
top: 10px;
right: 10px;
background: rgba(0,0,0,0.8);
color: white;
padding: 10px;
border-radius: 5px;
font-size: 12px;
max-width: 300px;
z-index: 1000;
}
</style>
</head>
<body>
<div id="dicomImage">
<div class="loading">
<div class="spinner"></div>
<div>Loading DICOM Viewer...</div>
</div>
</div>
<div id="debug" class="debug" style="display: none;">
<strong>Debug Info:</strong><br>
<div id="debugContent"></div>
</div>
<script>
// Global variables
let cornerstone = null;
let cornerstoneWADOImageLoader = null;
let dicomParser = null;
let isLoaded = false;
let currentDicomUrl = null;
let debugMode = true;
// Debug logging function
function debugLog(message, type = 'info') {
if (debugMode) {
console.log(`[DICOM Viewer] ${message}`);
const debugContent = document.getElementById('debugContent');
if (debugContent) {
const timestamp = new Date().toLocaleTimeString();
debugContent.innerHTML += `<div>[${timestamp}] ${message}</div>`;
}
}
}
// Show debug panel
function showDebug() {
if (debugMode) {
document.getElementById('debug').style.display = 'block';
}
}
// Function to load external libraries with retry
async function loadLibraries() {
try {
debugLog('Starting library loading process...');
showDebug();
// Load DICOM Parser first
await loadDicomParser();
// Load Cornerstone Core
await loadCornerstoneCore();
// Load Cornerstone WADO Image Loader with fallback
await loadCornerstoneWADO();
// Initialize viewer
initializeViewer();
// If we already have a DICOM URL, load it
if (currentDicomUrl) {
loadDicomImage(currentDicomUrl);
}
} catch (error) {
debugLog(`Error loading libraries: ${error.message}`, 'error');
showError(`Failed to load DICOM viewer libraries: ${error.message}`);
}
}
// Load DICOM Parser
function loadDicomParser() {
return new Promise((resolve, reject) => {
debugLog('Loading DICOM Parser...');
const dicomParserScript = document.createElement('script');
dicomParserScript.src = 'https://unpkg.com/dicom-parser@1.8.6/dist/dicomParser.min.js';
dicomParserScript.onload = () => {
dicomParser = window.dicomParser;
debugLog('DICOM Parser loaded successfully');
resolve();
};
dicomParserScript.onerror = (error) => {
debugLog(`Failed to load DICOM Parser: ${error}`, 'error');
reject(new Error('Failed to load DICOM Parser'));
};
// Set timeout for loading
const timeout = setTimeout(() => {
reject(new Error('DICOM Parser loading timeout'));
}, 10000);
dicomParserScript.onload = () => {
clearTimeout(timeout);
dicomParser = window.dicomParser;
debugLog('DICOM Parser loaded successfully');
resolve();
};
document.head.appendChild(dicomParserScript);
});
}
// Load Cornerstone Core
function loadCornerstoneCore() {
return new Promise((resolve, reject) => {
debugLog('Loading Cornerstone Core...');
const cornerstoneScript = document.createElement('script');
cornerstoneScript.src = 'https://unpkg.com/cornerstone-core@2.3.0/dist/cornerstone.js';
cornerstoneScript.onload = () => {
cornerstone = window.cornerstone;
debugLog('Cornerstone Core loaded successfully');
resolve();
};
cornerstoneScript.onerror = (error) => {
debugLog(`Failed to load Cornerstone Core: ${error}`, 'error');
reject(new Error('Failed to load Cornerstone Core'));
};
// Set timeout for loading
const timeout = setTimeout(() => {
reject(new Error('Cornerstone Core loading timeout'));
}, 10000);
cornerstoneScript.onload = () => {
clearTimeout(timeout);
cornerstone = window.cornerstone;
debugLog('Cornerstone Core loaded successfully');
resolve();
};
document.head.appendChild(cornerstoneScript);
});
}
// Load Cornerstone WADO Image Loader with fallback
function loadCornerstoneWADO() {
return new Promise((resolve, reject) => {
debugLog('Loading Cornerstone WADO Image Loader...');
// Try multiple sources for WADO loader
const wadoSources = [
'https://unpkg.com/cornerstone-wado-image-loader@4.17.1/dist/cornerstoneWADOImageLoader.js',
'https://unpkg.com/cornerstone-wado-image-loader@4.16.0/dist/cornerstoneWADOImageLoader.js',
'https://unpkg.com/cornerstone-wado-image-loader@4.15.0/dist/cornerstoneWADOImageLoader.js',
'https://cdn.jsdelivr.net/npm/cornerstone-wado-image-loader@4.0.1/dist/cornerstoneWADOImageLoader.js'
];
let currentSourceIndex = 0;
function tryNextSource() {
if (currentSourceIndex >= wadoSources.length) {
reject(new Error('All WADO Image Loader sources failed'));
return;
}
const currentSource = wadoSources[currentSourceIndex];
debugLog(`Trying WADO source ${currentSourceIndex + 1}: ${currentSource}`);
const wadoScript = document.createElement('script');
wadoScript.src = currentSource;
wadoScript.onload = () => {
try {
cornerstoneWADOImageLoader = window.cornerstoneWADOImageLoader;
if (cornerstoneWADOImageLoader && cornerstone) {
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
debugLog(`Cornerstone WADO Image Loader loaded successfully from: ${currentSource}`);
resolve();
} else {
throw new Error('WADO loader not properly initialized');
}
} catch (error) {
debugLog(`WADO loader initialization failed: ${error.message}`, 'error');
currentSourceIndex++;
tryNextSource();
}
};
wadoScript.onerror = (error) => {
debugLog(`Failed to load WADO from ${currentSource}: ${JSON.stringify(error)}`, 'error');
currentSourceIndex++;
tryNextSource();
};
// Set timeout for loading
const timeout = setTimeout(() => {
debugLog(`WADO loader timeout from ${currentSource}`, 'error');
currentSourceIndex++;
tryNextSource();
}, 8000);
wadoScript.onload = () => {
clearTimeout(timeout);
try {
cornerstoneWADOImageLoader = window.cornerstoneWADOImageLoader;
if (cornerstoneWADOImageLoader && cornerstone) {
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
debugLog(`Cornerstone WADO Image Loader loaded successfully from: ${currentSource}`);
resolve();
} else {
throw new Error('WADO loader not properly initialized');
}
} catch (error) {
debugLog(`WADO loader initialization failed: ${error.message}`, 'error');
currentSourceIndex++;
tryNextSource();
}
};
document.head.appendChild(wadoScript);
}
tryNextSource();
});
}
// Initialize the viewer
function initializeViewer() {
try {
const element = document.getElementById('dicomImage');
cornerstone.enable(element);
isLoaded = true;
debugLog('Cornerstone viewer initialized successfully');
} catch (error) {
debugLog(`Error initializing viewer: ${error.message}`, 'error');
showError(`Failed to initialize DICOM viewer: ${error.message}`);
}
}
// Load DICOM image with better error handling
async function loadDicomImage(dicomUrl) {
if (!isLoaded) {
debugLog('Libraries not loaded yet, storing URL');
currentDicomUrl = dicomUrl;
return;
}
try {
showLoading();
debugLog(`Loading DICOM image from: ${dicomUrl}`);
// Test if URL is accessible first
await testUrlAccessibility(dicomUrl);
// Try to load with DICOM Parser first for validation
try {
await validateDicomWithParser(dicomUrl);
debugLog('DICOM file validated with parser');
} catch (parserError) {
debugLog(`DICOM parser validation failed: ${parserError.message}`, 'warning');
// Continue anyway, as Cornerstone might still be able to handle it
}
const element = document.getElementById('dicomImage');
const image = await cornerstone.loadImage(`wadouri:${dicomUrl}`);
cornerstone.displayImage(element, image);
showSuccess('DICOM image loaded successfully');
debugLog('DICOM image loaded successfully');
// Send success message to React Native
sendMessageToReactNative({
type: 'success',
message: 'DICOM image loaded successfully'
});
} catch (error) {
debugLog(`Error loading DICOM image: ${error.message}`, 'error');
showError(`Failed to load DICOM image: ${error.message}`);
// Send error message to React Native
sendMessageToReactNative({
type: 'error',
message: `Failed to load DICOM image: ${error.message}`
});
}
}
// Validate DICOM file with parser
async function validateDicomWithParser(url) {
try {
debugLog('Validating DICOM file with parser...');
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
if (!dicomParser) {
throw new Error('DICOM Parser not available');
}
const dataSet = dicomParser.parseDicom(arrayBuffer);
debugLog(`DICOM file validated. Patient: ${dataSet.string('x00100010') || 'Unknown'}`);
debugLog(`Modality: ${dataSet.string('x00080060') || 'Unknown'}`);
debugLog(`Study Date: ${dataSet.string('x00080020') || 'Unknown'}`);
return dataSet;
} catch (error) {
debugLog(`DICOM parser validation failed: ${error.message}`, 'warning');
throw error;
}
}
// Test URL accessibility
async function testUrlAccessibility(url) {
try {
debugLog(`Testing URL accessibility: ${url}`);
const response = await fetch(url, { method: 'HEAD' });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const contentType = response.headers.get('content-type');
debugLog(`Content-Type: ${contentType}`);
if (contentType && !contentType.includes('application/dicom') && !contentType.includes('image/')) {
debugLog(`Warning: Unexpected content type: ${contentType}`);
}
} catch (error) {
debugLog(`URL accessibility test failed: ${error.message}`, 'error');
throw new Error(`URL not accessible: ${error.message}`);
}
}
// Send message to React Native
function sendMessageToReactNative(data) {
if (window.ReactNativeWebView) {
try {
const message = JSON.stringify(data);
window.ReactNativeWebView.postMessage(message);
debugLog(`Message sent to React Native: ${message}`);
} catch (error) {
debugLog(`Failed to send message to React Native: ${error.message}`, 'error');
}
} else {
debugLog('ReactNativeWebView not available');
}
}
// Show loading state
function showLoading() {
const element = document.getElementById('dicomImage');
element.innerHTML = `
<div class="loading">
<div class="spinner"></div>
<div>Loading DICOM Image...</div>
</div>
`;
}
// Show error state
function showError(message) {
const element = document.getElementById('dicomImage');
element.innerHTML = `
<div class="error">
<h3>Error</h3>
<p>${message}</p>
<p>Please check the DICOM URL and try again.</p>
<button onclick="retryLoading()" style="background: #2196F3; color: white; border: none; padding: 10px 20px; border-radius: 5px; margin-top: 10px;">Retry</button>
</div>
`;
}
// Show success state
function showSuccess(message) {
const element = document.getElementById('dicomImage');
element.innerHTML = `
<div class="success">
<h3>Success</h3>
<p>${message}</p>
</div>
`;
// Clear success message after 2 seconds
setTimeout(() => {
if (element.querySelector('.success')) {
element.innerHTML = '';
}
}, 2000);
}
// Retry loading
function retryLoading() {
if (currentDicomUrl) {
debugLog('Retrying DICOM image loading...');
loadDicomImage(currentDicomUrl);
}
}
// Listen for messages from React Native
window.addEventListener('message', function(event) {
try {
const dicomUrl = event.data;
debugLog(`Received DICOM URL from React Native: ${dicomUrl}`);
if (dicomUrl && typeof dicomUrl === 'string') {
currentDicomUrl = dicomUrl;
if (isLoaded) {
loadDicomImage(dicomUrl);
}
}
} catch (error) {
debugLog(`Error processing message from React Native: ${error.message}`, 'error');
showError('Invalid message received from app');
}
});
// Start loading libraries when page loads
window.addEventListener('load', () => {
debugLog('Page loaded, starting library loading...');
loadLibraries();
});
// Send ready message to React Native
sendMessageToReactNative({
type: 'ready',
message: 'DICOM viewer HTML loaded with DICOM Parser support'
});
// Global error handler
window.addEventListener('error', function(event) {
debugLog(`Global error: ${event.error?.message || event.message}`, 'error');
});
// Unhandled promise rejection handler
window.addEventListener('unhandledrejection', function(event) {
debugLog(`Unhandled promise rejection: ${event.reason}`, 'error');
});
</script>
</body>
</html>

View File

@ -0,0 +1,443 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DICOM Viewer Test</title>
<style>
body {
margin: 0;
padding: 20px;
background: #f5f5f5;
font-family: Arial, sans-serif;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.test-section {
margin-bottom: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
}
.url-input {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 10px;
}
.load-button {
background: #2196F3;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
.load-button:hover {
background: #1976D2;
}
.viewer-container {
margin-top: 20px;
border: 2px solid #ddd;
border-radius: 8px;
overflow: hidden;
min-height: 400px;
}
.status {
padding: 10px;
background: #f0f0f0;
border-bottom: 1px solid #ddd;
font-weight: bold;
}
#dicomImage {
width: 100%;
height: 400px;
background: #000;
display: flex;
justify-content: center;
align-items: center;
color: white;
}
.error {
color: #F44336;
background: #FFEBEE;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.success {
color: #4CAF50;
background: #E8F5E8;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.warning {
color: #FF9800;
background: #FFF3E0;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.sample-urls {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 10px;
margin-bottom: 20px;
}
.sample-url {
background: #E3F2FD;
padding: 10px;
border-radius: 4px;
cursor: pointer;
border: 1px solid #BBDEFB;
}
.sample-url:hover {
background: #BBDEFB;
}
.dicom-info {
background: #F5F5F5;
padding: 15px;
border-radius: 4px;
margin: 15px 0;
font-family: monospace;
font-size: 12px;
}
</style>
</head>
<body>
<div class="container">
<h1>DICOM Viewer Test</h1>
<p>Test the DICOM viewer functionality in your browser before using it in React Native.</p>
<div class="test-section">
<h3>Sample DICOM URLs</h3>
<div class="sample-urls">
<div class="sample-url" onclick="loadSampleUrl('https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm')">
<strong>Sample 1:</strong><br>
LIDC-IDRI-0001
</div>
<div class="sample-url" onclick="loadSampleUrl('https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-002.dcm')">
<strong>Sample 2:</strong><br>
LIDC-IDRI-0001
</div>
<div class="sample-url" onclick="loadSampleUrl('https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-003.dcm')">
<strong>Sample 3:</strong><br>
LIDC-IDRI-0001
</div>
</div>
</div>
<div class="test-section">
<h3>Custom DICOM URL</h3>
<input type="text" id="customUrl" class="url-input" placeholder="Enter DICOM URL here..." />
<button onclick="loadCustomUrl()" class="load-button">Load DICOM Image</button>
</div>
<div class="viewer-container">
<div class="status" id="status">Ready to load DICOM image</div>
<div id="dicomImage">
<div>Click a sample URL above or enter a custom URL to load a DICOM image</div>
</div>
</div>
<div id="dicomInfo" class="dicom-info" style="display: none;">
<strong>DICOM Information:</strong><br>
<div id="dicomInfoContent"></div>
</div>
<div id="messages"></div>
</div>
<script>
let cornerstone = null;
let cornerstoneWADOImageLoader = null;
let dicomParser = null;
let isLoaded = false;
// Load sample URL
function loadSampleUrl(url) {
document.getElementById('customUrl').value = url;
loadDicomImage(url);
}
// Load custom URL
function loadCustomUrl() {
const url = document.getElementById('customUrl').value.trim();
if (url) {
loadDicomImage(url);
} else {
showMessage('Please enter a valid URL', 'error');
}
}
// Show message
function showMessage(message, type = 'info') {
const messagesDiv = document.getElementById('messages');
const messageDiv = document.createElement('div');
messageDiv.className = type;
messageDiv.textContent = message;
messagesDiv.appendChild(messageDiv);
// Remove message after 5 seconds
setTimeout(() => {
messageDiv.remove();
}, 5000);
}
// Update status
function updateStatus(message) {
document.getElementById('status').textContent = message;
}
// Show DICOM info
function showDicomInfo(info) {
const infoDiv = document.getElementById('dicomInfo');
const contentDiv = document.getElementById('dicomInfoContent');
if (info) {
contentDiv.innerHTML = info;
infoDiv.style.display = 'block';
} else {
infoDiv.style.display = 'none';
}
}
// Load libraries
async function loadLibraries() {
try {
updateStatus('Loading DICOM viewer libraries...');
// Load DICOM Parser first
await loadScript('https://unpkg.com/dicom-parser@1.8.6/dist/dicomParser.min.js');
dicomParser = window.dicomParser;
showMessage('DICOM Parser loaded successfully', 'success');
// Load Cornerstone Core
await loadScript('https://unpkg.com/cornerstone-core@2.3.0/dist/cornerstone.js');
cornerstone = window.cornerstone;
// Load Cornerstone WADO Image Loader with fallback
await loadCornerstoneWADO();
isLoaded = true;
updateStatus('Libraries loaded successfully');
showMessage('All DICOM viewer libraries loaded successfully', 'success');
// Initialize viewer
const element = document.getElementById('dicomImage');
cornerstone.enable(element);
} catch (error) {
updateStatus('Failed to load libraries');
showMessage(`Failed to load libraries: ${error.message}`, 'error');
}
}
// Load script
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
document.head.appendChild(script);
});
}
// Load Cornerstone WADO Image Loader with fallback
function loadCornerstoneWADO() {
return new Promise((resolve, reject) => {
updateStatus('Loading Cornerstone WADO Image Loader...');
// Try multiple sources for WADO loader
const wadoSources = [
'https://unpkg.com/cornerstone-wado-image-loader@4.17.1/dist/cornerstoneWADOImageLoader.js',
'https://unpkg.com/cornerstone-wado-image-loader@4.16.0/dist/cornerstoneWADOImageLoader.js',
'https://unpkg.com/cornerstone-wado-image-loader@4.15.0/dist/cornerstoneWADOImageLoader.js',
'https://cdn.jsdelivr.net/npm/cornerstone-wado-image-loader@4.17.1/dist/cornerstoneWADOImageLoader.js'
];
let currentSourceIndex = 0;
function tryNextSource() {
if (currentSourceIndex >= wadoSources.length) {
reject(new Error('All WADO Image Loader sources failed'));
return;
}
const currentSource = wadoSources[currentSourceIndex];
updateStatus(`Trying WADO source ${currentSourceIndex + 1}: ${currentSource.split('/').pop()}`);
const wadoScript = document.createElement('script');
wadoScript.src = currentSource;
wadoScript.onload = () => {
try {
cornerstoneWADOImageLoader = window.cornerstoneWADOImageLoader;
if (cornerstoneWADOImageLoader && cornerstone) {
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
showMessage(`WADO Image Loader loaded successfully from: ${currentSource.split('/').pop()}`, 'success');
resolve();
} else {
throw new Error('WADO loader not properly initialized');
}
} catch (error) {
showMessage(`WADO loader initialization failed: ${error.message}`, 'warning');
currentSourceIndex++;
tryNextSource();
}
};
wadoScript.onerror = (error) => {
showMessage(`Failed to load WADO from ${currentSource.split('/').pop()}`, 'warning');
currentSourceIndex++;
tryNextSource();
};
// Set timeout for loading
const timeout = setTimeout(() => {
showMessage(`WADO loader timeout from ${currentSource.split('/').pop()}`, 'warning');
currentSourceIndex++;
tryNextSource();
}, 8000);
wadoScript.onload = () => {
clearTimeout(timeout);
try {
cornerstoneWADOImageLoader = window.cornerstoneWADOImageLoader;
if (cornerstoneWADOImageLoader && cornerstone) {
cornerstoneWADOImageLoader.external.cornerstone = cornerstone;
showMessage(`WADO Image Loader loaded successfully from: ${currentSource.split('/').pop()}`, 'success');
resolve();
} else {
throw new Error('WADO loader not properly initialized');
}
} catch (error) {
showMessage(`WADO loader initialization failed: ${error.message}`, 'warning');
currentSourceIndex++;
tryNextSource();
}
};
document.head.appendChild(wadoScript);
}
tryNextSource();
});
}
// Load DICOM image
async function loadDicomImage(url) {
if (!isLoaded) {
showMessage('Libraries not loaded yet, please wait...', 'error');
return;
}
try {
updateStatus('Loading DICOM image...');
showMessage(`Loading DICOM image from: ${url}`, 'info');
// Test URL accessibility
await testUrl(url);
// Validate DICOM with parser
let dicomInfo = null;
try {
dicomInfo = await validateDicomWithParser(url);
showMessage('DICOM file validated successfully', 'success');
} catch (parserError) {
showMessage(`DICOM validation warning: ${parserError.message}`, 'warning');
}
const element = document.getElementById('dicomImage');
const image = await cornerstone.loadImage(`wadouri:${url}`);
cornerstone.displayImage(element, image);
updateStatus('DICOM image loaded successfully');
showMessage('DICOM image loaded successfully!', 'success');
// Display DICOM information if available
if (dicomInfo) {
displayDicomInfo(dicomInfo);
}
} catch (error) {
updateStatus('Failed to load DICOM image');
showMessage(`Failed to load DICOM image: ${error.message}`, 'error');
showDicomInfo(null);
}
}
// Validate DICOM with parser
async function validateDicomWithParser(url) {
try {
const response = await fetch(url);
const arrayBuffer = await response.arrayBuffer();
if (!dicomParser) {
throw new Error('DICOM Parser not available');
}
const dataSet = dicomParser.parseDicom(arrayBuffer);
return dataSet;
} catch (error) {
throw new Error(`DICOM validation failed: ${error.message}`);
}
}
// Display DICOM information
function displayDicomInfo(dataSet) {
try {
const info = [
`Patient Name: ${dataSet.string('x00100010') || 'Unknown'}`,
`Patient ID: ${dataSet.string('x00100020') || 'Unknown'}`,
`Modality: ${dataSet.string('x00080060') || 'Unknown'}`,
`Study Date: ${dataSet.string('x00080020') || 'Unknown'}`,
`Study Description: ${dataSet.string('x00081030') || 'Unknown'}`,
`Manufacturer: ${dataSet.string('x00080070') || 'Unknown'}`,
`Image Size: ${dataSet.uint16('x00280010') || 'Unknown'} x ${dataSet.uint16('x00280011') || 'Unknown'}`,
`Bits Allocated: ${dataSet.uint16('x00280100') || 'Unknown'}`,
`Samples per Pixel: ${dataSet.uint16('x00280002') || 'Unknown'}`
].join('<br>');
showDicomInfo(info);
} catch (error) {
console.error('Error displaying DICOM info:', error);
showDicomInfo('Error displaying DICOM information');
}
}
// Test URL accessibility
async function testUrl(url) {
try {
const response = await fetch(url, { method: 'HEAD' });
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const contentType = response.headers.get('content-type');
console.log('Content-Type:', contentType);
if (contentType && !contentType.includes('application/dicom') && !contentType.includes('image/')) {
console.warn('Warning: Unexpected content type:', contentType);
}
} catch (error) {
throw new Error(`URL not accessible: ${error.message}`);
}
}
// Initialize when page loads
window.addEventListener('load', loadLibraries);
</script>
</body>
</html>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -13,7 +13,7 @@ import { theme } from '../../../theme';
// Import screens
import { AIPredictionsScreen, AIPredictionDetailScreen } from '../screens';
import { ComingSoonScreen } from '../../../shared/components';
import { ComingSoonScreen, DicomViewer } from '../../../shared/components';
// Import types
import type { AIPredictionStackParamList } from './navigationTypes';
@ -152,7 +152,7 @@ const AIPredictionStackNavigator: React.FC = () => {
{/* AI Prediction Details Screen */}
<Stack.Screen
name="AIPredictionDetails"
component={AIPredictionDetailScreen}
component={() => <DicomViewer dicomUrl={'https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm'} />}
options={({ navigation, route }) => ({
title: 'Create Suggestion',
headerLeft: () => (

File diff suppressed because it is too large Load Diff

View File

@ -365,7 +365,7 @@ const styles = StyleSheet.create({
// Header section
header: {
marginBottom: theme.spacing.lg,
// marginBottom: theme.spacing.lg,
},
// Main title
@ -465,10 +465,8 @@ const styles = StyleSheet.create({
// Pie chart container
pieChartContainer: {
alignItems: 'center',
marginBottom: theme.spacing.lg,
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.medium,
padding: theme.spacing.md,
},
// Legend container
@ -478,7 +476,6 @@ const styles = StyleSheet.create({
backgroundColor: theme.colors.backgroundAlt,
borderRadius: theme.borderRadius.medium,
padding: theme.spacing.md,
marginTop: theme.spacing.md,
},
// Legend title

View File

@ -0,0 +1,503 @@
/*
* File: ImageViewer.tsx
* Description: Full-screen DICOM image viewer with zoom, pan, and navigation
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useState, useCallback } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Dimensions,
Image,
ScrollView,
StatusBar,
SafeAreaView,
} from 'react-native';
import { theme } from '../../../theme/theme';
import Icon from 'react-native-vector-icons/Feather';
import { API_CONFIG } from '../../../shared/utils';
// Get screen dimensions
const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
// ============================================================================
// INTERFACES
// ============================================================================
interface ImageViewerProps {
visible: boolean;
images: string[];
initialIndex: number;
onClose: () => void;
patientName?: string;
seriesInfo?: string;
}
// ============================================================================
// IMAGE VIEWER COMPONENT
// ============================================================================
/**
* ImageViewer Component
*
* Purpose: Full-screen DICOM image viewer with advanced viewing capabilities
*
* Features:
* - Full-screen image display
* - Image navigation (previous/next)
* - Zoom and pan functionality
* - Patient information display
* - Series information
* - Touch gestures for navigation
* - Professional medical imaging interface
*/
const ImageViewer: React.FC<ImageViewerProps> = ({
visible,
images,
initialIndex,
onClose,
patientName = 'Unknown Patient',
seriesInfo = 'DICOM Series',
}) => {
// ============================================================================
// STATE MANAGEMENT
// ============================================================================
const [currentIndex, setCurrentIndex] = useState(initialIndex);
const [scale, setScale] = useState(1);
const [isZoomed, setIsZoomed] = useState(false);
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Handle Previous Image
*
* Purpose: Navigate to previous image in series
*/
const handlePrevious = useCallback(() => {
if (currentIndex > 0) {
setCurrentIndex(currentIndex - 1);
setScale(1);
setIsZoomed(false);
}
}, [currentIndex]);
/**
* Handle Next Image
*
* Purpose: Navigate to next image in series
*/
const handleNext = useCallback(() => {
if (currentIndex < images.length - 1) {
setCurrentIndex(currentIndex + 1);
setScale(1);
setIsZoomed(false);
}
}, [currentIndex, images.length]);
/**
* Handle Zoom In
*
* Purpose: Increase image zoom level
*/
const handleZoomIn = useCallback(() => {
const newScale = Math.min(scale * 1.5, 3);
setScale(newScale);
setIsZoomed(newScale > 1);
}, [scale]);
/**
* Handle Zoom Out
*
* Purpose: Decrease image zoom level
*/
const handleZoomOut = useCallback(() => {
const newScale = Math.max(scale / 1.5, 0.5);
setScale(newScale);
setIsZoomed(newScale > 1);
}, [scale]);
/**
* Handle Reset Zoom
*
* Purpose: Reset image to original size
*/
const handleResetZoom = useCallback(() => {
setScale(1);
setIsZoomed(false);
}, []);
/**
* Handle Close
*
* Purpose: Close image viewer and return to previous screen
*/
const handleClose = useCallback(() => {
setScale(1);
setIsZoomed(false);
setCurrentIndex(initialIndex);
onClose();
}, [initialIndex, onClose]);
// ============================================================================
// RENDER HELPERS
// ============================================================================
/**
* Render Header
*
* Purpose: Render image viewer header with patient info and controls
*/
const renderHeader = () => (
<View style={styles.header}>
<View style={styles.headerLeft}>
<TouchableOpacity
style={styles.closeButton}
onPress={handleClose}
>
<Icon name="x" size={24} color={theme.colors.background} />
</TouchableOpacity>
<View style={styles.patientInfo}>
<Text style={styles.patientName}>{patientName}</Text>
<Text style={styles.seriesInfo}>{seriesInfo}</Text>
</View>
</View>
<View style={styles.headerRight}>
<Text style={styles.imageCounter}>
{currentIndex + 1} of {images.length}
</Text>
</View>
</View>
);
/**
* Render Navigation Controls
*
* Purpose: Render image navigation controls
*/
const renderNavigationControls = () => (
<View style={styles.navigationControls}>
<TouchableOpacity
style={[
styles.navButton,
currentIndex === 0 && styles.navButtonDisabled
]}
onPress={handlePrevious}
disabled={currentIndex === 0}
>
<Icon
name="chevron-left"
size={24}
color={currentIndex === 0 ? theme.colors.textMuted : theme.colors.background}
/>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.navButton,
currentIndex === images.length - 1 && styles.navButtonDisabled
]}
onPress={handleNext}
disabled={currentIndex === images.length - 1}
>
<Icon
name="chevron-right"
size={24}
color={currentIndex === images.length - 1 ? theme.colors.textMuted : theme.colors.background}
/>
</TouchableOpacity>
</View>
);
/**
* Render Zoom Controls
*
* Purpose: Render zoom control buttons
*/
const renderZoomControls = () => (
<View style={styles.zoomControls}>
<TouchableOpacity
style={styles.zoomButton}
onPress={handleZoomOut}
>
<Icon name="minus" size={20} color={theme.colors.background} />
</TouchableOpacity>
<TouchableOpacity
style={styles.zoomButton}
onPress={handleResetZoom}
>
<Text style={styles.zoomText}>{Math.round(scale * 100)}%</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.zoomButton}
onPress={handleZoomIn}
>
<Icon name="plus" size={20} color={theme.colors.background} />
</TouchableOpacity>
</View>
);
// ============================================================================
// MAIN RENDER
// ============================================================================
if (!visible || images.length === 0) {
return null;
}
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="light-content" backgroundColor="#000000" />
{/* Header */}
{renderHeader()}
{/* Main Image Area */}
<View style={styles.imageContainer}>
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
maximumZoomScale={3}
minimumZoomScale={0.5}
bounces={false}
>
<Image
source={{ uri: API_CONFIG.DICOM_BASE_URL + images[currentIndex] }}
style={[
styles.image,
{
transform: [{ scale }],
},
]}
resizeMode="contain"
/>
</ScrollView>
</View>
{/* Navigation Controls */}
{renderNavigationControls()}
{/* Zoom Controls */}
{renderZoomControls()}
{/* Thumbnail Strip */}
<View style={styles.thumbnailStrip}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.thumbnailContent}
>
{images.map((image, index) => (
<TouchableOpacity
key={index}
style={[
styles.thumbnail,
index === currentIndex && styles.activeThumbnail
]}
onPress={() => setCurrentIndex(index)}
>
<Image
source={{ uri: API_CONFIG.DICOM_BASE_URL + image }}
style={styles.thumbnailImage}
resizeMode="cover"
/>
{index === currentIndex && (
<View style={styles.activeIndicator}>
<Icon name="check" size={12} color={theme.colors.background} />
</View>
)}
</TouchableOpacity>
))}
</ScrollView>
</View>
</SafeAreaView>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000000',
},
// Header Styles
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
},
headerLeft: {
flexDirection: 'row',
alignItems: 'center',
flex: 1,
},
closeButton: {
padding: theme.spacing.sm,
marginRight: theme.spacing.md,
},
patientInfo: {
flex: 1,
},
patientName: {
fontSize: 16,
fontWeight: 'bold',
color: theme.colors.background,
fontFamily: theme.typography.fontFamily.bold,
},
seriesInfo: {
fontSize: 12,
color: theme.colors.background,
opacity: 0.8,
fontFamily: theme.typography.fontFamily.regular,
},
headerRight: {
alignItems: 'flex-end',
},
imageCounter: {
fontSize: 14,
color: theme.colors.background,
fontFamily: theme.typography.fontFamily.medium,
},
// Image Container Styles
imageContainer: {
flex: 1,
// backgroundColor: '#000000',
},
scrollView: {
flex: 1,
},
scrollContent: {
flexGrow: 1,
justifyContent: 'center',
alignItems: 'center',
},
image: {
width: screenWidth,
height: screenHeight * 0.7,
},
// Navigation Controls Styles
navigationControls: {
position: 'absolute',
top: '50%',
left: 0,
right: 0,
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: theme.spacing.md,
transform: [{ translateY: -20 }],
},
navButton: {
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: 'rgba(0, 0, 0, 0.6)',
justifyContent: 'center',
alignItems: 'center',
shadowColor: '#000000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
elevation: 4,
},
navButtonDisabled: {
backgroundColor: 'rgba(0, 0, 0, 0.3)',
},
// Zoom Controls Styles
zoomControls: {
position: 'absolute',
bottom: 100,
right: theme.spacing.md,
flexDirection: 'column',
alignItems: 'center',
},
zoomButton: {
width: 44,
height: 44,
borderRadius: 22,
backgroundColor: 'rgba(0, 0, 0, 0.6)',
justifyContent: 'center',
alignItems: 'center',
marginBottom: theme.spacing.sm,
shadowColor: '#000000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
elevation: 4,
},
zoomText: {
color: theme.colors.background,
fontSize: 12,
fontWeight: 'bold',
fontFamily: theme.typography.fontFamily.bold,
},
// Thumbnail Strip Styles
thumbnailStrip: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
backgroundColor: 'rgba(0, 0, 0, 0.8)',
paddingVertical: theme.spacing.sm,
},
thumbnailContent: {
paddingHorizontal: theme.spacing.md,
},
thumbnail: {
width: 60,
height: 60,
borderRadius: 8,
marginRight: theme.spacing.sm,
position: 'relative',
borderWidth: 2,
borderColor: 'transparent',
},
activeThumbnail: {
borderColor: theme.colors.primary,
},
thumbnailImage: {
width: '100%',
height: '100%',
borderRadius: 6,
},
activeIndicator: {
position: 'absolute',
top: -4,
right: -4,
width: 20,
height: 20,
borderRadius: 10,
backgroundColor: theme.colors.primary,
justifyContent: 'center',
alignItems: 'center',
},
});
export default ImageViewer;
/*
* End of File: ImageViewer.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -158,7 +158,7 @@ const PatientCard: React.FC<PatientCardProps> = ({
const patientDetails = parseJsonSafely(patient.patientdetails);
const patientData = patientDetails.patientdetails || patientDetails;
const series = parseJsonSafely(patient.series);
const series = parseJsonSafely(patientDetails.series);
const typeConfig = getCaseTypeConfig(patient.type);
// ============================================================================
@ -207,7 +207,7 @@ const PatientCard: React.FC<PatientCardProps> = ({
<TouchableOpacity
style={[
styles.container,
patient.type === 'Critical' && styles.containerCritical,
// patient.type === 'Critical' && styles.containerCritical,
{ borderLeftColor: typeConfig.color }
]}
onPress={onPress}

View File

@ -10,6 +10,7 @@ export { default as SearchBar } from './SearchBar';
export { default as FilterTabs } from './FilterTabs';
export { default as EmptyState } from './EmptyState';
export { default as LoadingState } from './LoadingState';
export { default as ImageViewer } from './ImageViewer';
/*
* End of File: index.ts

View File

@ -0,0 +1,76 @@
/*
* File: PatientCareStackNavigator.tsx
* Description: Stack navigator for PatientCare module navigation
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
// Import screens
import { PatientsScreen, PatientDetailsScreen } from '../screens';
// Import types
import { PatientCareStackParamList } from './navigationTypes';
// ============================================================================
// STACK NAVIGATOR
// ============================================================================
const Stack = createStackNavigator<PatientCareStackParamList>();
/**
* PatientCareStackNavigator Component
*
* Purpose: Provides stack navigation for PatientCare module
*
* Screens:
* - PatientsScreen: Main patient list screen
* - PatientDetailsScreen: Detailed patient information and DICOM images
*
* Navigation Flow:
* PatientsScreen PatientDetailsScreen (with patient data)
*/
const PatientCareStackNavigator: React.FC = () => {
return (
<Stack.Navigator
initialRouteName="PatientsScreen"
screenOptions={{
headerShown: false,
cardStyle: { backgroundColor: 'transparent' },
cardOverlayEnabled: false,
gestureEnabled: true,
gestureDirection: 'horizontal',
}}
>
{/* Patients Screen - Main patient list */}
<Stack.Screen
name="PatientsScreen"
component={PatientsScreen}
options={{
title: 'Patients',
}}
/>
{/* Patient Details Screen - Comprehensive patient information */}
<Stack.Screen
name="PatientDetails"
component={PatientDetailsScreen}
options={{
title: 'Patient Details',
gestureEnabled: true,
gestureDirection: 'horizontal',
}}
/>
</Stack.Navigator>
);
};
export default PatientCareStackNavigator;
/*
* End of File: PatientCareStackNavigator.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,15 @@
/*
* File: index.ts
* Description: Barrel export for PatientCare navigation components
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as PatientCareStackNavigator } from './PatientCareStackNavigator';
export * from './navigationTypes';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,118 @@
/*
* File: navigationTypes.ts
* Description: TypeScript types for PatientCare module navigation
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { StackNavigationProp } from '@react-navigation/stack';
import { MedicalCase } from '../../../shared/types';
// ============================================================================
// NAVIGATION PARAMETER LISTS
// ============================================================================
/**
* PatientCareStackParamList - Defines the parameter list for PatientCare stack navigator
*
* This interface defines all the screens available in the PatientCare module
* and their associated navigation parameters.
*/
export type PatientCareStackParamList = {
// Patients Screen - Main patient list with search and filtering
PatientsScreen: PatientsScreenParams;
// Patient Details Screen - Comprehensive patient information and DICOM images
PatientDetails: PatientDetailsScreenParams;
};
// ============================================================================
// SCREEN PARAMETER INTERFACES
// ============================================================================
/**
* PatientsScreenParams
*
* Purpose: Parameters for the patients list screen
*
* Parameters:
* - None required - this is the main entry point
*/
export interface PatientsScreenParams {
// No parameters required for main patients screen
}
/**
* PatientDetailsScreenParams
*
* Purpose: Parameters for the patient details screen
*
* Parameters:
* - patientId: Required patient ID to display details
* - patientName: Required patient name for display
* - medicalCase: Required medical case data with full patient information
*/
export interface PatientDetailsScreenParams {
patientId: string;
patientName: string;
medicalCase: MedicalCase;
}
// ============================================================================
// NAVIGATION PROP TYPES
// ============================================================================
/**
* PatientCareNavigationProp - Navigation prop type for PatientCare screens
*
* Purpose: Provides type-safe navigation methods for PatientCare module screens
*/
export type PatientCareNavigationProp = StackNavigationProp<PatientCareStackParamList>;
// ============================================================================
// SCREEN PROP TYPES
// ============================================================================
/**
* PatientsScreenProps - Props for PatientsScreen component
*/
export interface PatientsScreenProps {
navigation: PatientCareNavigationProp;
route: {
params: PatientsScreenParams;
};
}
/**
* PatientDetailsScreenProps - Props for PatientDetailsScreen component
*/
export interface PatientDetailsScreenProps {
navigation: PatientCareNavigationProp;
route: {
params: PatientDetailsScreenParams;
};
}
// ============================================================================
// NAVIGATION UTILITY TYPES
// ============================================================================
/**
* NavigationHelper - Helper type for navigation functions
*
* Purpose: Provides type-safe navigation helper functions
*/
export type NavigationHelper = {
navigateToPatientDetails: (
patientId: string,
patientName: string,
medicalCase: MedicalCase
) => void;
navigateBack: () => void;
};
/*
* End of File: navigationTypes.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -26,7 +26,6 @@ export const fetchPatients = createAsyncThunk(
try {
// Make actual API call to fetch medical cases
const response :any = await patientAPI.getPatients(token);
console.log('patients response',response)
if (response.ok && response.data&&response.data.success) {
// Add random case types to each patient record
const caseTypes: Array<'Critical' | 'Emergency' | 'Routine'> = ['Critical', 'Emergency', 'Routine'];
@ -36,7 +35,6 @@ export const fetchPatients = createAsyncThunk(
type: caseTypes[Math.floor(Math.random() * caseTypes.length)]
}));
console.log('patients with random types', patientsWithTypes);
return patientsWithTypes as MedicalCase[];
} else {
// Fallback to mock data for development
@ -125,7 +123,7 @@ export const fetchPatientDetails = createAsyncThunk(
async (patientId: string, { rejectWithValue }) => {
try {
// TODO: Replace with actual API call
await new Promise((resolve) => setTimeout(resolve, 1000));
await new Promise((resolve) => setTimeout(resolve as any, 1000));
// Mock patient details for specific patient
const mockPatient: MedicalCase = {
@ -179,7 +177,7 @@ export const updatePatient = createAsyncThunk(
async (patientData: Partial<MedicalCase> & { id: number }, { rejectWithValue }) => {
try {
// TODO: Replace with actual API call
await new Promise((resolve) => setTimeout(resolve, 800));
await new Promise((resolve) => setTimeout(resolve as any, 800));
return patientData;
} catch (error) {
return rejectWithValue('Failed to update patient.');
@ -388,8 +386,8 @@ const patientCareSlice = createSlice({
state.isLoading = false;
state.patients = action.payload;
state.totalItems = action.payload.length;
state.lastUpdated = new Date();
state.cacheExpiry = new Date(Date.now() + 5 * 60 * 1000); // 5 minutes
state.lastUpdated = new Date().toLocaleDateString();
state.cacheExpiry = new Date(Date.now() + 5 * 60 * 1000).toLocaleDateString(); // 5 minutes
state.error = null;
})
.addCase(fetchPatients.rejected, (state, action) => {

File diff suppressed because it is too large Load Diff

View File

@ -56,6 +56,7 @@ import LoadingState from '../components/LoadingState';
// Import types
import { MedicalCase, PatientDetails, Series } from '../../../shared/types';
import { PatientsScreenProps } from '../navigation/navigationTypes';
// Get screen dimensions
const { width: screenWidth } = Dimensions.get('window');
@ -64,10 +65,6 @@ const { width: screenWidth } = Dimensions.get('window');
// INTERFACES
// ============================================================================
interface PatientsScreenProps {
navigation: any;
}
// ============================================================================
// PATIENTS SCREEN COMPONENT
// ============================================================================
@ -241,7 +238,7 @@ const PatientsScreen: React.FC<PatientsScreenProps> = ({ navigation }) => {
const patientData = patientDetails.patientdetails || patientDetails;
navigation.navigate('PatientDetails', {
patientId: patient.id,
patientId:'1',
patientName: patientData.Name || 'Unknown Patient',
medicalCase: patient,
});
@ -397,7 +394,7 @@ const PatientsScreen: React.FC<PatientsScreenProps> = ({ navigation }) => {
style={styles.headerButton}
onPress={() => {
// TODO: Implement notifications screen
navigation.navigate('Notifications');
Alert.alert('Notifications', 'Notifications feature coming soon');
}}
>
<Icon name="bell" size={20} color={theme.colors.textSecondary} />
@ -473,15 +470,15 @@ const PatientsScreen: React.FC<PatientsScreenProps> = ({ navigation }) => {
/>
}
// Performance optimizations
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={10}
initialNumToRender={8}
getItemLayout={(data, index) => ({
length: 120, // Approximate height of PatientCard
offset: 120 * index,
index,
})}
// removeClippedSubviews={true}
// maxToRenderPerBatch={10}
// windowSize={10}
// initialNumToRender={8}
// getItemLayout={(data, index) => ({
// length: 120, // Approximate height of PatientCard
// offset: 120 * index,
// index,
// })}
/>
</SafeAreaView>
);

View File

@ -6,6 +6,7 @@
*/
export { default as PatientsScreen } from './PatientsScreen';
export { default as PatientDetailsScreen } from './PatientDetailsScreen';
/*
* End of File: index.ts

View File

@ -34,7 +34,7 @@ const Stack = createStackNavigator<SettingsStackParamList>();
const SettingsStackNavigator: React.FC = () => {
return (
<Stack.Navigator
initialRouteName="Settings"
initialRouteName="SettingScreen"
screenOptions={{
// Header styling for settings screens
headerStyle: {
@ -71,7 +71,7 @@ const SettingsStackNavigator: React.FC = () => {
>
{/* Settings Screen - Main settings entry point */}
<Stack.Screen
name="Settings"
name="SettingScreen"
component={SettingsScreen}
options={{
title: 'Settings',

View File

@ -16,7 +16,7 @@ import { UserProfile, UserPreferences } from '../../../shared/types';
*/
export type SettingsStackParamList = {
// Settings screen - Main settings with profile and preferences
Settings: SettingsScreenParams;
SettingScreen: SettingsScreenParams;
// Profile Edit screen - Edit user profile information
ProfileEdit: ProfileEditScreenParams;

View File

@ -108,7 +108,7 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
const userProfilePhoto = useAppSelector(selectUserProfilePhoto);
const notificationPreferences = useAppSelector(selectNotificationPreferences);
const dashboardSettings = useAppSelector(selectDashboardSettings);
console.log('user details i got', user);
// ============================================================================
// SETTINGS SECTIONS GENERATION

View File

@ -14,7 +14,7 @@ import { AIPredictionStackNavigator } from '../modules/AIPrediction/navigation';
import { MainTabParamList } from './navigationTypes';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import { ComingSoonScreen } from '../shared/components';
import { PatientsScreen } from '../modules/PatientCare';
import { PatientCareStackNavigator } from '../modules/PatientCare/navigation';
// Create the bottom tab navigator
const Tab = createBottomTabNavigator<MainTabParamList>();
@ -86,7 +86,7 @@ export const MainTabNavigator: React.FC = () => {
{/* Patients Tab - Patient list and management */}
<Tab.Screen
name="Patients"
component={PatientsScreen} // TODO: Replace with actual PatientsScreen
component={PatientCareStackNavigator}
options={{
title: 'Patient List',
tabBarLabel: 'Patients',

View File

@ -0,0 +1,207 @@
# DICOM Viewer Component
## Overview
The DICOM Viewer component is a React Native component that uses WebView to display DICOM medical imaging files. It integrates with Cornerstone.js and Cornerstone WADO Image Loader for robust DICOM file handling.
## Features
- ✅ WebView-based DICOM rendering
- ✅ Cornerstone.js integration for medical imaging
- ✅ Support for remote DICOM URLs
- ✅ Loading states and error handling
- ✅ Real-time communication with React Native
- ✅ Responsive design for mobile devices
## Components
### 1. DicomViewer
The main DICOM viewer component.
**Props:**
```typescript
interface DicomViewerProps {
dicomUrl: string; // URL to the DICOM file
onError?: (error: string) => void; // Error callback
onLoad?: () => void; // Load success callback
}
```
**Usage:**
```typescript
import { DicomViewer } from '../shared/components';
<DicomViewer
dicomUrl="https://example.com/sample.dcm"
onError={(error) => console.error('DICOM Error:', error)}
onLoad={() => console.log('DICOM loaded successfully')}
/>
```
### 2. DicomViewerTest
A test component for testing different DICOM URLs and debugging issues.
**Usage:**
```typescript
import { DicomViewerTest } from '../shared/components';
<DicomViewerTest />
```
## How It Works
### 1. WebView Setup
- Loads a local HTML file (`dicom-viewer.html`)
- Enables JavaScript and DOM storage
- Allows file access and universal access from file URLs
### 2. Library Loading
- Dynamically loads Cornerstone.js from CDN
- Loads Cornerstone WADO Image Loader
- Initializes the viewer when libraries are ready
### 3. DICOM Processing
- Receives DICOM URL from React Native via postMessage
- Uses Cornerstone to load and display the DICOM image
- Handles errors and success states
### 4. Communication
- Sends status messages back to React Native
- Reports loading, success, and error states
- Enables debugging and user feedback
## Troubleshooting
### Black Screen Issues
#### 1. Check Console Logs
Open the React Native debugger and check for:
- WebView loading errors
- JavaScript execution errors
- Network request failures
#### 2. Verify DICOM URL
- Ensure the URL is accessible from the device
- Check if the URL returns a valid DICOM file
- Verify CORS settings if loading from a different domain
#### 3. Library Loading Issues
- Check internet connectivity (libraries load from CDN)
- Verify the HTML file path is correct
- Check WebView permissions and settings
#### 4. Platform-Specific Issues
**Android:**
- Ensure `allowFileAccess` is enabled
- Check if the HTML file is in the correct assets folder
- Verify WebView permissions in AndroidManifest.xml
**iOS:**
- Check WebView configuration in Info.plist
- Ensure JavaScript is enabled
- Verify file access permissions
### Common Error Messages
#### "Failed to load DICOM viewer libraries"
- Check internet connectivity
- Verify CDN URLs are accessible
- Check WebView JavaScript settings
#### "Failed to load DICOM image"
- Verify DICOM URL is accessible
- Check if the file is a valid DICOM format
- Ensure the server supports CORS
#### "Invalid message received from app"
- Check the message format being sent
- Verify the postMessage implementation
- Check WebView message handling
## Testing
### 1. Use Sample URLs
The test component includes sample DICOM URLs that are known to work:
- Sample DICOM 1-3 from OHIF examples
### 2. Test Custom URLs
- Enter your own DICOM URLs
- Test with different file formats
- Verify error handling
### 3. Debug Mode
- Check console logs in React Native debugger
- Monitor WebView messages
- Use the test component for isolated testing
## Performance Tips
### 1. Image Optimization
- Use compressed DICOM files when possible
- Consider implementing progressive loading
- Cache frequently accessed images
### 2. Memory Management
- Dispose of WebView when not in use
- Monitor memory usage with large DICOM files
- Implement proper cleanup in useEffect
### 3. Network Optimization
- Use CDN for DICOM files when possible
- Implement retry logic for failed requests
- Consider offline caching for critical images
## Security Considerations
### 1. URL Validation
- Validate DICOM URLs before loading
- Implement URL whitelisting if needed
- Sanitize user input for custom URLs
### 2. WebView Security
- Limit WebView permissions to minimum required
- Implement proper origin whitelisting
- Monitor for malicious content
### 3. Data Privacy
- Ensure DICOM files don't contain PHI
- Implement proper data handling protocols
- Follow HIPAA compliance guidelines
## Future Enhancements
### 1. Offline Support
- Bundle Cornerstone libraries locally
- Implement offline DICOM caching
- Support for local DICOM files
### 2. Advanced Features
- Multi-planar reconstruction (MPR)
- Measurement tools
- Annotation capabilities
- 3D rendering support
### 3. Performance Improvements
- WebAssembly integration
- GPU acceleration
- Progressive image loading
- Background processing
## Support
For issues and questions:
1. Check this README for common solutions
2. Review console logs and error messages
3. Test with sample URLs first
4. Verify WebView configuration
5. Check platform-specific requirements
## Dependencies
- `react-native-webview`: WebView component
- `cornerstone-core`: Medical imaging library
- `cornerstone-wado-image-loader`: DICOM file loader
## License
Design & Developed by Tech4Biz Solutions
Copyright (c) Spurrin Innovations. All rights reserved.

View File

@ -0,0 +1,306 @@
/*
* File: DicomViewer.tsx
* Description: DICOM viewer component using WebView for medical imaging
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useRef, useEffect, useState } from 'react';
import { WebView, WebViewMessageEvent } from 'react-native-webview';
import { Platform, View, Text, StyleSheet, ActivityIndicator, TouchableOpacity } from 'react-native';
// Interface for component props
interface DicomViewerProps {
dicomUrl: string;
onError?: (error: string) => void;
onLoad?: () => void;
debugMode?: boolean;
}
// Interface for WebView reference
interface WebViewRef {
postMessage: (message: string) => void;
reload: () => void;
}
export default function DicomViewer({ dicomUrl, onError, onLoad, debugMode = false }: DicomViewerProps): React.ReactElement {
const webViewRef = useRef<WebViewRef>(null);
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
const [debugInfo, setDebugInfo] = useState<string[]>([]);
const [webViewReady, setWebViewReady] = useState(false);
// Debug logging function
const debugLog = (message: string) => {
if (debugMode) {
const timestamp = new Date().toLocaleTimeString();
const logMessage = `[${timestamp}] ${message}`;
console.log(logMessage);
setDebugInfo(prev => [...prev.slice(-9), logMessage]); // Keep last 10 messages
}
};
// Handle WebView load events
const handleLoadStart = () => {
debugLog('WebView load started');
setIsLoading(true);
setHasError(false);
};
const handleLoadEnd = () => {
debugLog('WebView load ended');
setIsLoading(false);
setWebViewReady(true);
onLoad?.();
};
const handleError = (error: any) => {
debugLog(`WebView error: ${JSON.stringify(error)}`);
setIsLoading(false);
setHasError(true);
onError?.(error?.nativeEvent?.description || 'Failed to load DICOM viewer');
};
const handleMessage = (event: WebViewMessageEvent) => {
try {
const message = event.nativeEvent.data;
debugLog(`Message from WebView: ${message}`);
// Try to parse JSON message
if (typeof message === 'string') {
try {
const parsedMessage = JSON.parse(message);
debugLog(`Parsed message: ${JSON.stringify(parsedMessage)}`);
if (parsedMessage.type === 'error') {
setHasError(true);
onError?.(parsedMessage.message);
} else if (parsedMessage.type === 'success') {
setHasError(false);
}
} catch (parseError) {
debugLog(`Failed to parse message as JSON: ${parseError}`);
}
}
} catch (error) {
debugLog(`Error handling WebView message: ${error}`);
}
};
// Send DICOM URL to WebView when component mounts or URL changes
useEffect(() => {
if (webViewRef.current && dicomUrl && webViewReady) {
debugLog(`Sending DICOM URL to WebView: ${dicomUrl}`);
// Wait a bit for WebView to be ready
const timer = setTimeout(() => {
if (webViewRef.current) {
try {
webViewRef.current.postMessage(dicomUrl);
debugLog('DICOM URL sent successfully');
} catch (error) {
debugLog(`Failed to send DICOM URL: ${error}`);
}
}
}, 1000);
return () => clearTimeout(timer);
}
}, [dicomUrl, webViewReady]);
// Reload WebView if there's an error
const handleRetry = () => {
debugLog('Retrying WebView load');
if (webViewRef.current) {
setHasError(false);
setIsLoading(true);
setWebViewReady(false);
webViewRef.current.reload();
}
};
// Clear debug info
const clearDebugInfo = () => {
setDebugInfo([]);
};
return (
<View style={styles.container}>
<WebView
ref={webViewRef as any}
source={require('../../assets/dicom/dicom-viewer.html')}
originWhitelist={['*']}
javaScriptEnabled
domStorageEnabled
allowFileAccess
allowUniversalAccessFromFileURLs
allowFileAccessFromFileURLs
onLoadStart={handleLoadStart}
onLoadEnd={handleLoadEnd}
onError={handleError}
onMessage={handleMessage}
style={styles.webview}
startInLoadingState
renderLoading={() => (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#2196F3" />
<Text style={styles.loadingText}>Loading DICOM Viewer...</Text>
</View>
)}
/>
{hasError && (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>Failed to load DICOM viewer</Text>
<Text style={styles.errorDetails}>
URL: {dicomUrl}
</Text>
<TouchableOpacity style={styles.retryButton} onPress={handleRetry}>
<Text style={styles.retryButtonText}>Tap to retry</Text>
</TouchableOpacity>
</View>
)}
{debugMode && (
<View style={styles.debugContainer}>
<View style={styles.debugHeader}>
<Text style={styles.debugTitle}>Debug Info</Text>
<TouchableOpacity onPress={clearDebugInfo}>
<Text style={styles.clearButton}>Clear</Text>
</TouchableOpacity>
</View>
<View style={styles.debugContent}>
{debugInfo.map((info, index) => (
<Text key={index} style={styles.debugText}>{info}</Text>
))}
</View>
<View style={styles.debugStatus}>
<Text style={styles.debugStatusText}>
WebView Ready: {webViewReady ? 'Yes' : 'No'}
</Text>
<Text style={styles.debugStatusText}>
Loading: {isLoading ? 'Yes' : 'No'}
</Text>
<Text style={styles.debugStatusText}>
Error: {hasError ? 'Yes' : 'No'}
</Text>
</View>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
webview: {
flex: 1,
backgroundColor: '#000',
},
loadingContainer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#000',
},
loadingText: {
color: '#FFF',
marginTop: 16,
fontSize: 16,
},
errorContainer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#000',
padding: 20,
},
errorText: {
color: '#F44336',
fontSize: 18,
textAlign: 'center',
marginBottom: 16,
fontWeight: '600',
},
errorDetails: {
color: '#FF9800',
fontSize: 14,
textAlign: 'center',
marginBottom: 20,
fontFamily: 'monospace',
},
retryButton: {
backgroundColor: '#2196F3',
paddingHorizontal: 24,
paddingVertical: 12,
borderRadius: 8,
},
retryButtonText: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '600',
},
debugContainer: {
position: 'absolute',
top: 10,
right: 10,
backgroundColor: 'rgba(0,0,0,0.9)',
borderRadius: 8,
padding: 10,
maxWidth: 300,
maxHeight: 400,
},
debugHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 8,
},
debugTitle: {
color: '#FFFFFF',
fontSize: 14,
fontWeight: '600',
},
clearButton: {
color: '#2196F3',
fontSize: 12,
textDecorationLine: 'underline',
},
debugContent: {
maxHeight: 200,
},
debugText: {
color: '#FFFFFF',
fontSize: 10,
fontFamily: 'monospace',
marginBottom: 2,
},
debugStatus: {
marginTop: 8,
paddingTop: 8,
borderTopColor: '#333',
borderTopWidth: 1,
},
debugStatusText: {
color: '#CCC',
fontSize: 10,
marginBottom: 2,
},
});
/*
* End of File: DicomViewer.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,252 @@
/*
* File: DicomViewerTest.tsx
* Description: Test component for DICOM viewer with sample URLs
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useState } from 'react';
import { View, Text, StyleSheet, TextInput, TouchableOpacity, ScrollView, Alert } from 'react-native';
import DicomViewer from './DicomViewer';
// Sample DICOM URLs for testing
const SAMPLE_DICOM_URLS = [
{
name: 'Sample DICOM 1',
url: 'https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-001.dcm'
},
{
name: 'Sample DICOM 2',
url: 'https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-002.dcm'
},
{
name: 'Sample DICOM 3',
url: 'https://ohif-dicom-json-example.s3.amazonaws.com/LIDC-IDRI-0001/01-01-2000-30178/3000566.000000-03192/1-003.dcm'
}
];
export default function DicomViewerTest(): React.ReactElement {
const [dicomUrl, setDicomUrl] = useState(SAMPLE_DICOM_URLS[0].url);
const [customUrl, setCustomUrl] = useState('');
const handleUrlSelect = (url: string) => {
setDicomUrl(url);
setCustomUrl(url);
};
const handleCustomUrlSubmit = () => {
if (customUrl.trim()) {
setDicomUrl(customUrl.trim());
} else {
Alert.alert('Error', 'Please enter a valid URL');
}
};
const handleViewerError = (error: string) => {
console.error('DICOM Viewer Error:', error);
Alert.alert('DICOM Viewer Error', error);
};
const handleViewerLoad = () => {
console.log('DICOM Viewer loaded successfully');
};
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>DICOM Viewer Test</Text>
<Text style={styles.subtitle}>Test different DICOM URLs</Text>
</View>
<ScrollView style={styles.urlSection}>
<Text style={styles.sectionTitle}>Sample DICOM URLs:</Text>
{SAMPLE_DICOM_URLS.map((sample, index) => (
<TouchableOpacity
key={index}
style={[
styles.urlButton,
dicomUrl === sample.url && styles.selectedUrlButton
]}
onPress={() => handleUrlSelect(sample.url)}
>
<Text style={[
styles.urlButtonText,
dicomUrl === sample.url && styles.selectedUrlButtonText
]}>
{sample.name}
</Text>
<Text style={[
styles.urlText,
dicomUrl === sample.url && styles.selectedUrlText
]}>
{sample.url}
</Text>
</TouchableOpacity>
))}
<View style={styles.customUrlSection}>
<Text style={styles.sectionTitle}>Custom DICOM URL:</Text>
<View style={styles.inputContainer}>
<TextInput
style={styles.textInput}
value={customUrl}
onChangeText={setCustomUrl}
placeholder="Enter DICOM URL..."
placeholderTextColor="#999"
autoCapitalize="none"
autoCorrect={false}
/>
<TouchableOpacity
style={styles.submitButton}
onPress={handleCustomUrlSubmit}
>
<Text style={styles.submitButtonText}>Load</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.currentUrlSection}>
<Text style={styles.sectionTitle}>Current URL:</Text>
<Text style={styles.currentUrl}>{dicomUrl}</Text>
</View>
</ScrollView>
<View style={styles.viewerContainer}>
<Text style={styles.viewerTitle}>DICOM Viewer:</Text>
<DicomViewer
dicomUrl={dicomUrl}
onError={handleViewerError}
onLoad={handleViewerLoad}
debugMode={true}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F5F5',
},
header: {
backgroundColor: '#2196F3',
padding: 20,
alignItems: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#FFFFFF',
marginBottom: 8,
},
subtitle: {
fontSize: 16,
color: '#E3F2FD',
},
urlSection: {
flex: 1,
padding: 16,
},
sectionTitle: {
fontSize: 18,
fontWeight: '600',
color: '#212121',
marginBottom: 12,
marginTop: 16,
},
urlButton: {
backgroundColor: '#FFFFFF',
padding: 16,
borderRadius: 8,
marginBottom: 8,
borderWidth: 1,
borderColor: '#E0E0E0',
},
selectedUrlButton: {
backgroundColor: '#E3F2FD',
borderColor: '#2196F3',
},
urlButtonText: {
fontSize: 16,
fontWeight: '600',
color: '#212121',
marginBottom: 4,
},
selectedUrlButtonText: {
color: '#1976D2',
},
urlText: {
fontSize: 12,
color: '#757575',
fontFamily: 'monospace',
},
selectedUrlText: {
color: '#1976D2',
},
customUrlSection: {
marginTop: 16,
},
inputContainer: {
flexDirection: 'row',
alignItems: 'center',
},
textInput: {
flex: 1,
backgroundColor: '#FFFFFF',
borderWidth: 1,
borderColor: '#E0E0E0',
borderRadius: 8,
padding: 12,
fontSize: 14,
color: '#212121',
marginRight: 8,
},
submitButton: {
backgroundColor: '#4CAF50',
paddingHorizontal: 20,
paddingVertical: 12,
borderRadius: 8,
},
submitButtonText: {
color: '#FFFFFF',
fontSize: 14,
fontWeight: '600',
},
currentUrlSection: {
marginTop: 16,
backgroundColor: '#FFFFFF',
padding: 16,
borderRadius: 8,
borderWidth: 1,
borderColor: '#E0E0E0',
},
currentUrl: {
fontSize: 12,
color: '#757575',
fontFamily: 'monospace',
backgroundColor: '#F5F5F5',
padding: 8,
borderRadius: 4,
},
viewerContainer: {
flex: 2,
backgroundColor: '#000000',
margin: 16,
borderRadius: 8,
overflow: 'hidden',
},
viewerTitle: {
color: '#FFFFFF',
fontSize: 16,
fontWeight: '600',
padding: 12,
backgroundColor: '#333333',
},
});
/*
* End of File: DicomViewerTest.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -15,6 +15,12 @@ export { CustomModal } from './CustomModal';
// Coming Soon Screen Component
export { ComingSoonScreen } from './ComingSoonScreen';
// DICOM Viewer Component
export { default as DicomViewer } from './DicomViewer';
// DICOM Viewer Test Component
export { default as DicomViewerTest } from './DicomViewerTest';
// ============================================================================
// TYPE EXPORTS
// ============================================================================

View File

@ -150,8 +150,8 @@ export interface PatientCareState {
totalItems: number;
// Cache
lastUpdated: Date | null;
cacheExpiry: Date | null;
lastUpdated: string | null;
cacheExpiry: string | null;
}
/*

View File

@ -9,6 +9,7 @@ import Config from 'react-native-config';
// API Configuration
export const API_CONFIG = {
BASE_URL:Config.BASE_URL,
DICOM_BASE_URL:'https://demo.medpacsystems.com',
TIMEOUT: 30000,
RETRY_ATTEMPTS: 3,
RETRY_DELAY: 1000,

View File

@ -8,12 +8,12 @@
export const typography = {
// Font Families
fontFamily: {
bold: 'Roboto-Bold',
medium: 'Roboto-Medium',
regular: 'Roboto-Regular',
light: 'Roboto-Light',
semibold: 'Roboto-SemiBold',
extrabold: 'Roboto-ExtraBold',
bold: 'WorkSans-Bold',
medium: 'WorkSans-Medium',
regular: 'WorkSans-Regular',
light: 'WorkSans-Light',
semibold: 'WorkSans-SemiBold',
extrabold: 'WorkSans-ExtraBold',
},
// Font Weights

View File

@ -11,14 +11,14 @@
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
9BFF7E8F967043379EA34EF5 /* Roboto-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C93B020BF2D44311BFAF317D /* Roboto-Black.ttf */; };
64ADC498A16B4DD2979F9EC6 /* Roboto-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E8DE31AC3A3B46AFBAB8E623 /* Roboto-Bold.ttf */; };
9A05BCBBB57F42CF891B4E76 /* Roboto-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6D158A6D27784CB3AAFC4572 /* Roboto-ExtraBold.ttf */; };
E9AB985237534D1A92E3A5C8 /* Roboto-ExtraLight.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6787284EF58F40B4A29BE810 /* Roboto-ExtraLight.ttf */; };
A345282A09764F4B8935E1CE /* Roboto-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = F3B7B24AB39048E28AA013C6 /* Roboto-Light.ttf */; };
6C3A045CF24641D79616809F /* Roboto-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 57615910A7564380B1D23731 /* Roboto-Medium.ttf */; };
A79B0AFEC3DA42AAAA56DC75 /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B9D41D27862846A8A6563185 /* Roboto-Regular.ttf */; };
264B0BD7896E430EB2761212 /* Roboto-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7539BBD08F0743178F5517A3 /* Roboto-SemiBold.ttf */; };
90EC1A731F2F480594C0F9D1 /* WorkSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6606F41B1382422DA695F61C /* WorkSans-Bold.ttf */; };
7E1975A765074059A27FA6F1 /* WorkSans-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 1569DC7537534ED39A79EE9E /* WorkSans-ExtraBold.ttf */; };
E09CC0AFCD63425FABA5714F /* WorkSans-ExtraLight.ttf in Resources */ = {isa = PBXBuildFile; fileRef = FF48E43749B74116A2C4083C /* WorkSans-ExtraLight.ttf */; };
6A06861DDC314E49B482B4EB /* WorkSans-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B8C4C96FCA144E859DC2AFDA /* WorkSans-Light.ttf */; };
3BEFE3BEC3334662878A37D5 /* WorkSans-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 55E647ABF16B47F6A83403C0 /* WorkSans-Medium.ttf */; };
6E9607F0F9DE4649802D618B /* WorkSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 589149FBA9F64E9F94AA50AF /* WorkSans-Regular.ttf */; };
DCDB35B731E1422DBD1481D1 /* WorkSans-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A58733FB00504503BFBF4295 /* WorkSans-SemiBold.ttf */; };
25B52D1C5AB64F039BEF062F /* WorkSans-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C3614D19A4004E3E858E4CEC /* WorkSans-Thin.ttf */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -43,14 +43,14 @@
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = NeoScan_Physician/AppDelegate.swift; sourceTree = "<group>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = NeoScan_Physician/LaunchScreen.storyboard; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
C93B020BF2D44311BFAF317D /* Roboto-Black.ttf */ = {isa = PBXFileReference; name = "Roboto-Black.ttf"; path = "../app/assets/fonts/Roboto-Black.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
E8DE31AC3A3B46AFBAB8E623 /* Roboto-Bold.ttf */ = {isa = PBXFileReference; name = "Roboto-Bold.ttf"; path = "../app/assets/fonts/Roboto-Bold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
6D158A6D27784CB3AAFC4572 /* Roboto-ExtraBold.ttf */ = {isa = PBXFileReference; name = "Roboto-ExtraBold.ttf"; path = "../app/assets/fonts/Roboto-ExtraBold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
6787284EF58F40B4A29BE810 /* Roboto-ExtraLight.ttf */ = {isa = PBXFileReference; name = "Roboto-ExtraLight.ttf"; path = "../app/assets/fonts/Roboto-ExtraLight.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
F3B7B24AB39048E28AA013C6 /* Roboto-Light.ttf */ = {isa = PBXFileReference; name = "Roboto-Light.ttf"; path = "../app/assets/fonts/Roboto-Light.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
57615910A7564380B1D23731 /* Roboto-Medium.ttf */ = {isa = PBXFileReference; name = "Roboto-Medium.ttf"; path = "../app/assets/fonts/Roboto-Medium.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
B9D41D27862846A8A6563185 /* Roboto-Regular.ttf */ = {isa = PBXFileReference; name = "Roboto-Regular.ttf"; path = "../app/assets/fonts/Roboto-Regular.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
7539BBD08F0743178F5517A3 /* Roboto-SemiBold.ttf */ = {isa = PBXFileReference; name = "Roboto-SemiBold.ttf"; path = "../app/assets/fonts/Roboto-SemiBold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
6606F41B1382422DA695F61C /* WorkSans-Bold.ttf */ = {isa = PBXFileReference; name = "WorkSans-Bold.ttf"; path = "../app/assets/fonts/WorkSans-Bold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
1569DC7537534ED39A79EE9E /* WorkSans-ExtraBold.ttf */ = {isa = PBXFileReference; name = "WorkSans-ExtraBold.ttf"; path = "../app/assets/fonts/WorkSans-ExtraBold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
FF48E43749B74116A2C4083C /* WorkSans-ExtraLight.ttf */ = {isa = PBXFileReference; name = "WorkSans-ExtraLight.ttf"; path = "../app/assets/fonts/WorkSans-ExtraLight.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
B8C4C96FCA144E859DC2AFDA /* WorkSans-Light.ttf */ = {isa = PBXFileReference; name = "WorkSans-Light.ttf"; path = "../app/assets/fonts/WorkSans-Light.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
55E647ABF16B47F6A83403C0 /* WorkSans-Medium.ttf */ = {isa = PBXFileReference; name = "WorkSans-Medium.ttf"; path = "../app/assets/fonts/WorkSans-Medium.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
589149FBA9F64E9F94AA50AF /* WorkSans-Regular.ttf */ = {isa = PBXFileReference; name = "WorkSans-Regular.ttf"; path = "../app/assets/fonts/WorkSans-Regular.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
A58733FB00504503BFBF4295 /* WorkSans-SemiBold.ttf */ = {isa = PBXFileReference; name = "WorkSans-SemiBold.ttf"; path = "../app/assets/fonts/WorkSans-SemiBold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
C3614D19A4004E3E858E4CEC /* WorkSans-Thin.ttf */ = {isa = PBXFileReference; name = "WorkSans-Thin.ttf"; path = "../app/assets/fonts/WorkSans-Thin.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -136,14 +136,14 @@
C38CA987921A4CA4AEDBB3E5 /* Resources */ = {
isa = "PBXGroup";
children = (
C93B020BF2D44311BFAF317D /* Roboto-Black.ttf */,
E8DE31AC3A3B46AFBAB8E623 /* Roboto-Bold.ttf */,
6D158A6D27784CB3AAFC4572 /* Roboto-ExtraBold.ttf */,
6787284EF58F40B4A29BE810 /* Roboto-ExtraLight.ttf */,
F3B7B24AB39048E28AA013C6 /* Roboto-Light.ttf */,
57615910A7564380B1D23731 /* Roboto-Medium.ttf */,
B9D41D27862846A8A6563185 /* Roboto-Regular.ttf */,
7539BBD08F0743178F5517A3 /* Roboto-SemiBold.ttf */,
6606F41B1382422DA695F61C /* WorkSans-Bold.ttf */,
1569DC7537534ED39A79EE9E /* WorkSans-ExtraBold.ttf */,
FF48E43749B74116A2C4083C /* WorkSans-ExtraLight.ttf */,
B8C4C96FCA144E859DC2AFDA /* WorkSans-Light.ttf */,
55E647ABF16B47F6A83403C0 /* WorkSans-Medium.ttf */,
589149FBA9F64E9F94AA50AF /* WorkSans-Regular.ttf */,
A58733FB00504503BFBF4295 /* WorkSans-SemiBold.ttf */,
C3614D19A4004E3E858E4CEC /* WorkSans-Thin.ttf */,
);
name = Resources;
sourceTree = "<group>";
@ -218,14 +218,14 @@
files = (
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
9BFF7E8F967043379EA34EF5 /* Roboto-Black.ttf in Resources */,
64ADC498A16B4DD2979F9EC6 /* Roboto-Bold.ttf in Resources */,
9A05BCBBB57F42CF891B4E76 /* Roboto-ExtraBold.ttf in Resources */,
E9AB985237534D1A92E3A5C8 /* Roboto-ExtraLight.ttf in Resources */,
A345282A09764F4B8935E1CE /* Roboto-Light.ttf in Resources */,
6C3A045CF24641D79616809F /* Roboto-Medium.ttf in Resources */,
A79B0AFEC3DA42AAAA56DC75 /* Roboto-Regular.ttf in Resources */,
264B0BD7896E430EB2761212 /* Roboto-SemiBold.ttf in Resources */,
90EC1A731F2F480594C0F9D1 /* WorkSans-Bold.ttf in Resources */,
7E1975A765074059A27FA6F1 /* WorkSans-ExtraBold.ttf in Resources */,
E09CC0AFCD63425FABA5714F /* WorkSans-ExtraLight.ttf in Resources */,
6A06861DDC314E49B482B4EB /* WorkSans-Light.ttf in Resources */,
3BEFE3BEC3334662878A37D5 /* WorkSans-Medium.ttf in Resources */,
6E9607F0F9DE4649802D618B /* WorkSans-Regular.ttf in Resources */,
DCDB35B731E1422DBD1481D1 /* WorkSans-SemiBold.ttf in Resources */,
25B52D1C5AB64F039BEF062F /* WorkSans-Thin.ttf in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -49,17 +49,16 @@
<false/>
<key>UIAppFonts</key>
<array>
<string>Roboto-Black.ttf</string>
<string>Roboto-Bold.ttf</string>
<string>Roboto-ExtraBold.ttf</string>
<string>Roboto-ExtraLight.ttf</string>
<string>Roboto-Light.ttf</string>
<string>Roboto-Medium.ttf</string>
<string>Roboto-Regular.ttf</string>
<string>Roboto-SemiBold.ttf</string>
<string>WorkSans-Bold.ttf</string>
<string>WorkSans-ExtraBold.ttf</string>
<string>WorkSans-ExtraLight.ttf</string>
<string>WorkSans-Light.ttf</string>
<string>WorkSans-Medium.ttf</string>
<string>WorkSans-Regular.ttf</string>
<string>WorkSans-SemiBold.ttf</string>
<string>WorkSans-Thin.ttf</string>
</array>
<key>NSCameraUsageDescription</key>
<string>This app needs access to the camera to take photos and videos.</string>
<string>This app needs access to the camera to take photos and videos.</string>
</dict>
</plist>

View File

@ -2,36 +2,36 @@
"migIndex": 1,
"data": [
{
"path": "app/assets/fonts/Roboto-Black.ttf",
"sha1": "d1678489a8d5645f16486ec52d77b651ff0bf327"
"path": "app/assets/fonts/WorkSans-Bold.ttf",
"sha1": "ec84061651ead3c3c5cbb61c2d338aca0bacdc1e"
},
{
"path": "app/assets/fonts/Roboto-Bold.ttf",
"sha1": "508c35dee818addce6cc6d1fb6e42f039da5a7cf"
"path": "app/assets/fonts/WorkSans-ExtraBold.ttf",
"sha1": "0b371d1dbfbdd15db880bbd129b239530c71accb"
},
{
"path": "app/assets/fonts/Roboto-ExtraBold.ttf",
"sha1": "3dbfd71b6fbcfbd8e7ee8a8dd033dc5aaad63249"
"path": "app/assets/fonts/WorkSans-ExtraLight.ttf",
"sha1": "74596e55487e2961b6c43993698d658e2ceee77b"
},
{
"path": "app/assets/fonts/Roboto-ExtraLight.ttf",
"sha1": "df556e64732e5c272349e13cb5f87591a1ae779b"
"path": "app/assets/fonts/WorkSans-Light.ttf",
"sha1": "293e11dae7e8b930bf5eea0b06ca979531f22189"
},
{
"path": "app/assets/fonts/Roboto-Light.ttf",
"sha1": "318b44c0a32848f78bf11d4fbf3355d00647a796"
"path": "app/assets/fonts/WorkSans-Medium.ttf",
"sha1": "c281f8454dd193c2260e43ae2de171c5dd4086e4"
},
{
"path": "app/assets/fonts/Roboto-Medium.ttf",
"sha1": "fa5192203f85ddb667579e1bdf26f12098bb873b"
"path": "app/assets/fonts/WorkSans-Regular.ttf",
"sha1": "5e0183b29b57c54595c62ac6bc223b21f1434226"
},
{
"path": "app/assets/fonts/Roboto-Regular.ttf",
"sha1": "3bff51436aa7eb995d84cfc592cc63e1316bb400"
"path": "app/assets/fonts/WorkSans-SemiBold.ttf",
"sha1": "64b8fe156fafce221a0f66504255257053fc6062"
},
{
"path": "app/assets/fonts/Roboto-SemiBold.ttf",
"sha1": "9ca139684fe902c8310dd82991648376ac9838db"
"path": "app/assets/fonts/WorkSans-Thin.ttf",
"sha1": "a62251331038fdd079c47bc413a350efbf702db8"
}
]
}

15
package-lock.json generated
View File

@ -44,6 +44,7 @@
"react-native-toast-message": "^2.2.1",
"react-native-tts": "^4.1.1",
"react-native-vector-icons": "^10.2.0",
"react-native-webview": "^13.15.0",
"react-redux": "^9.2.0",
"redux-persist": "^6.0.0"
},
@ -12077,6 +12078,20 @@
"node": ">=10"
}
},
"node_modules/react-native-webview": {
"version": "13.15.0",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.15.0.tgz",
"integrity": "sha512-Vzjgy8mmxa/JO6l5KZrsTC7YemSdq+qB01diA0FqjUTaWGAGwuykpJ73MDj3+mzBSlaDxAEugHzTtkUQkQEQeQ==",
"license": "MIT",
"dependencies": {
"escape-string-regexp": "^4.0.0",
"invariant": "2.2.4"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native/node_modules/@react-native/virtualized-lists": {
"version": "0.79.0",
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.79.0.tgz",

View File

@ -46,6 +46,7 @@
"react-native-toast-message": "^2.2.1",
"react-native-tts": "^4.1.1",
"react-native-vector-icons": "^10.2.0",
"react-native-webview": "^13.15.0",
"react-redux": "^9.2.0",
"redux-persist": "^6.0.0"
},