patient api integrated dashboard ui created
This commit is contained in:
parent
1e2e4eba47
commit
4ff28e061b
BIN
android/app/src/main/assets/fonts/Roboto-Black.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Roboto-Black.ttf
Normal file
Binary file not shown.
BIN
android/app/src/main/assets/fonts/Roboto-Bold.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Roboto-Bold.ttf
Normal file
Binary file not shown.
BIN
android/app/src/main/assets/fonts/Roboto-ExtraBold.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Roboto-ExtraBold.ttf
Normal file
Binary file not shown.
BIN
android/app/src/main/assets/fonts/Roboto-ExtraLight.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Roboto-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
android/app/src/main/assets/fonts/Roboto-Light.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Roboto-Light.ttf
Normal file
Binary file not shown.
BIN
android/app/src/main/assets/fonts/Roboto-Medium.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Roboto-Medium.ttf
Normal file
Binary file not shown.
BIN
android/app/src/main/assets/fonts/Roboto-Regular.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Roboto-Regular.ttf
Normal file
Binary file not shown.
BIN
android/app/src/main/assets/fonts/Roboto-SemiBold.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Roboto-SemiBold.ttf
Normal file
Binary file not shown.
37
android/link-assets-manifest.json
Normal file
37
android/link-assets-manifest.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"migIndex": 1,
|
||||
"data": [
|
||||
{
|
||||
"path": "shared/src/assets/fonts/Roboto-Black.ttf",
|
||||
"sha1": "d1678489a8d5645f16486ec52d77b651ff0bf327"
|
||||
},
|
||||
{
|
||||
"path": "shared/src/assets/fonts/Roboto-Bold.ttf",
|
||||
"sha1": "508c35dee818addce6cc6d1fb6e42f039da5a7cf"
|
||||
},
|
||||
{
|
||||
"path": "shared/src/assets/fonts/Roboto-ExtraBold.ttf",
|
||||
"sha1": "3dbfd71b6fbcfbd8e7ee8a8dd033dc5aaad63249"
|
||||
},
|
||||
{
|
||||
"path": "shared/src/assets/fonts/Roboto-ExtraLight.ttf",
|
||||
"sha1": "df556e64732e5c272349e13cb5f87591a1ae779b"
|
||||
},
|
||||
{
|
||||
"path": "shared/src/assets/fonts/Roboto-Light.ttf",
|
||||
"sha1": "318b44c0a32848f78bf11d4fbf3355d00647a796"
|
||||
},
|
||||
{
|
||||
"path": "shared/src/assets/fonts/Roboto-Medium.ttf",
|
||||
"sha1": "fa5192203f85ddb667579e1bdf26f12098bb873b"
|
||||
},
|
||||
{
|
||||
"path": "shared/src/assets/fonts/Roboto-Regular.ttf",
|
||||
"sha1": "3bff51436aa7eb995d84cfc592cc63e1316bb400"
|
||||
},
|
||||
{
|
||||
"path": "shared/src/assets/fonts/Roboto-SemiBold.ttf",
|
||||
"sha1": "9ca139684fe902c8310dd82991648376ac9838db"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -9,11 +9,16 @@ import React from 'react';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import { DashboardScreen } from '../screens';
|
||||
import { Colors } from '../../../../shared/src/theme';
|
||||
import DashBoardDetail from '../screens/DashBoardDetail';
|
||||
|
||||
export type DashboardStackParamList = {
|
||||
Dashboard: undefined;
|
||||
// Add more screens here as needed
|
||||
DashboardScreen: undefined;
|
||||
DashboardDetailScreen:{
|
||||
caseType: 'Critical' | 'Routine' | 'Emergency';
|
||||
};
|
||||
// Add more screens here as needed
|
||||
|
||||
}
|
||||
|
||||
const Stack = createStackNavigator<DashboardStackParamList>();
|
||||
|
||||
@ -22,15 +27,20 @@ const Stack = createStackNavigator<DashboardStackParamList>();
|
||||
*/
|
||||
const DashboardNavigator: React.FC = () => (
|
||||
<Stack.Navigator
|
||||
initialRouteName="Dashboard"
|
||||
initialRouteName="DashboardScreen"
|
||||
screenOptions={{
|
||||
headerStyle: { backgroundColor: Colors.primary },
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="Dashboard"
|
||||
name="DashboardScreen"
|
||||
component={DashboardScreen}
|
||||
options={{ title: 'Dashboard' ,headerShown:false}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="DashboardDetailScreen"
|
||||
component={DashBoardDetail}
|
||||
options={{ title: 'Dashboard' ,headerShown:false}}
|
||||
/>
|
||||
{/* Add more screens here */}
|
||||
</Stack.Navigator>
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { fetchCasesStart, fetchCasesSuccess, fetchCasesFailure } from './dashboardSlice';
|
||||
import { fetchCasesStart, fetchCasesSuccess, fetchCasesFailure, fetchPatientsSuccess } from './dashboardSlice';
|
||||
import { caseAPI } from '../services/caseAPI';
|
||||
|
||||
/**
|
||||
@ -31,6 +31,23 @@ export const fetchCases = createAsyncThunk(
|
||||
}
|
||||
);
|
||||
|
||||
export const fetchPatients = createAsyncThunk(
|
||||
'dashboard/fetchPatients',
|
||||
async (payload, { dispatch, rejectWithValue }) => {
|
||||
try {
|
||||
dispatch(fetchCasesStart());
|
||||
const response :any = await caseAPI.getPatients(payload);
|
||||
console.log('response i got',response)
|
||||
if (response.ok && response.data && response.data.data) {
|
||||
dispatch(fetchPatientsSuccess(response.data.data));
|
||||
}
|
||||
} catch (error: any) {
|
||||
dispatch(fetchCasesFailure(error.message));
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
* End of File: dashboardActions.ts
|
||||
* Design & Developed by Tech4Biz Solutions
|
||||
|
||||
@ -10,6 +10,8 @@ import { RootState } from '../../../../app/redux/rootReducer';
|
||||
export const selectCaseQueue = (state: RootState) => state.dashboard.caseQueue;
|
||||
export const selectDashboardLoading = (state: RootState) => state.dashboard.loading;
|
||||
export const selectDashboardError = (state: RootState) => state.dashboard.error;
|
||||
export const selectPatients = (state: RootState) => state.dashboard.patientData;
|
||||
|
||||
|
||||
/*
|
||||
* End of File: dashboardSelectors.ts
|
||||
|
||||
@ -21,12 +21,14 @@ interface DashboardState {
|
||||
caseQueue: Case[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
patientData:[]
|
||||
}
|
||||
|
||||
const initialState: DashboardState = {
|
||||
caseQueue: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
patientData:[]
|
||||
};
|
||||
|
||||
/**
|
||||
@ -51,6 +53,23 @@ const dashboardSlice = createSlice({
|
||||
clearCases(state) {
|
||||
state.caseQueue = [];
|
||||
},
|
||||
fetchPatientsSuccess(state, action: PayloadAction<[]>) {
|
||||
const caseTypes: ('Critical' | 'Emergency' | 'Routine')[] = ['Critical', 'Emergency', 'Routine'];
|
||||
|
||||
// Function to get random case type
|
||||
const getRandomCaseType = (): 'Critical' | 'Emergency' | 'Routine' => {
|
||||
return caseTypes[Math.floor(Math.random() * caseTypes.length)];
|
||||
};
|
||||
|
||||
// Add type attribute to each patient
|
||||
const patientsWithType :any = action.payload.map((patient: any) => ({
|
||||
...patient,
|
||||
type: getRandomCaseType()
|
||||
}));
|
||||
|
||||
state.loading = false;
|
||||
state.patientData = patientsWithType;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -59,6 +78,7 @@ export const {
|
||||
fetchCasesSuccess,
|
||||
fetchCasesFailure,
|
||||
clearCases,
|
||||
fetchPatientsSuccess
|
||||
} = dashboardSlice.actions;
|
||||
|
||||
export default dashboardSlice.reducer;
|
||||
|
||||
@ -5,22 +5,356 @@
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text, StyleSheet, ScrollView } from 'react-native';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-native';
|
||||
import { Button } from '../../../../shared/src/components/Button';
|
||||
import { Card, InfoCard } from '../../../../shared/src/components/Card';
|
||||
import { SearchInput } from '../../../../shared/src/components/Input';
|
||||
import { Modal } from '../../../../shared/src/components/Modal';
|
||||
import { CustomIcon, IconButton } from '../../../../shared/src/components/Icons';
|
||||
import { CustomHeader } from '../../../../shared/src/components/Header';
|
||||
import { Colors, Spacing, Typography } from '../../../../shared/src/theme';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { selectPatients } from '../redux';
|
||||
|
||||
interface PatientDetails {
|
||||
Date: string;
|
||||
Name: string;
|
||||
PatID: string;
|
||||
PatAge: string;
|
||||
PatSex: string;
|
||||
Status: string;
|
||||
InstName: string;
|
||||
Modality: 'DX' | 'CT' | 'MR';
|
||||
ReportStatus: string | null;
|
||||
}
|
||||
|
||||
interface Series {
|
||||
Path: string[];
|
||||
SerDes: string;
|
||||
ViePos: string | null;
|
||||
pngpath: string;
|
||||
SeriesNum: string;
|
||||
ImgTotalinSeries: string;
|
||||
}
|
||||
|
||||
export interface MedicalCase {
|
||||
id: number;
|
||||
patientdetails: PatientDetails;
|
||||
series: Series[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
series_id: string | null;
|
||||
type: 'Critical' | 'Routine' | 'Emergency';
|
||||
}
|
||||
|
||||
interface DashBoardDetailProps {
|
||||
navigation: any;
|
||||
route: {
|
||||
params: {
|
||||
caseType: 'Critical' | 'Routine' | 'Emergency';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* DashboardScreen demonstrates usage of all shared, themed components.
|
||||
* This is a reference implementation for other modules.
|
||||
* DashBoardDetail - Shows detailed list of patients for a specific case type
|
||||
*/
|
||||
const DashBoardDetail: React.FC = () => {
|
||||
const DashBoardDetail: React.FC<DashBoardDetailProps> = ({ navigation, route }) => {
|
||||
const [search, setSearch] = useState('');
|
||||
const [selectedPatient, setSelectedPatient] = useState<MedicalCase | null>(null);
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [sortBy, setSortBy] = useState<'date' | 'name' | 'age'>('date');
|
||||
|
||||
const caseType = route.params?.caseType || 'Critical';
|
||||
const patientData: MedicalCase[] = useSelector(selectPatients) || [];
|
||||
|
||||
// Filter patients by case type and search
|
||||
const filteredPatients = useMemo(() => {
|
||||
let filtered = patientData.filter(case_ => case_.type === caseType);
|
||||
|
||||
if (search.trim()) {
|
||||
filtered = filtered.filter(case_ =>
|
||||
case_.patientdetails.Name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
case_.patientdetails.PatID.toLowerCase().includes(search.toLowerCase()) ||
|
||||
case_.patientdetails.InstName.toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
}
|
||||
|
||||
// Sort patients
|
||||
return filtered.sort((a, b) => {
|
||||
switch (sortBy) {
|
||||
case 'name':
|
||||
return a.patientdetails.Name.localeCompare(b.patientdetails.Name);
|
||||
case 'age':
|
||||
return parseInt(b.patientdetails.PatAge) - parseInt(a.patientdetails.PatAge);
|
||||
case 'date':
|
||||
default:
|
||||
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
|
||||
}
|
||||
});
|
||||
}, [patientData, caseType, search, sortBy]);
|
||||
|
||||
const getCaseTypeConfig = (type: string) => {
|
||||
switch (type) {
|
||||
case 'Critical':
|
||||
return {
|
||||
color: Colors.error,
|
||||
icon: 'alert-circle',
|
||||
bgColor: '#FFF5F5' // Light red background
|
||||
};
|
||||
case 'Emergency':
|
||||
return {
|
||||
color: '#FF8C00',
|
||||
icon: 'alert',
|
||||
bgColor: '#FFF8E1' // Light orange background
|
||||
};
|
||||
case 'Routine':
|
||||
return {
|
||||
color: Colors.success,
|
||||
icon: 'check-circle',
|
||||
bgColor: '#F0FFF4' // Light green background
|
||||
};
|
||||
default:
|
||||
return {
|
||||
color: Colors.primary,
|
||||
icon: 'info',
|
||||
bgColor: Colors.background
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const typeConfig = getCaseTypeConfig(caseType);
|
||||
|
||||
const handlePatientPress = (patient: MedicalCase) => {
|
||||
setSelectedPatient(patient);
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
};
|
||||
|
||||
const getModalityColor = (modality: string) => {
|
||||
switch (modality) {
|
||||
case 'CT': return '#4A90E2';
|
||||
case 'MR': return '#7B68EE';
|
||||
case 'DX': return '#50C878';
|
||||
default: return Colors.textSecondary;
|
||||
}
|
||||
};
|
||||
|
||||
const renderPatientCard = (patient: MedicalCase) => (
|
||||
<TouchableOpacity
|
||||
key={patient.id}
|
||||
onPress={() => handlePatientPress(patient)}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<Card style={[styles.patientCard, { borderLeftColor: typeConfig.color }]}>
|
||||
<View style={styles.cardHeader}>
|
||||
<View style={styles.patientInfo}>
|
||||
<Text style={styles.patientName}>
|
||||
{patient.patientdetails.Name}
|
||||
</Text>
|
||||
<Text style={styles.patientId}>
|
||||
ID: {patient.patientdetails.PatID}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.urgencyBadge}>
|
||||
<CustomIcon
|
||||
name={typeConfig.icon}
|
||||
color={typeConfig.color}
|
||||
size={16}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardBody}>
|
||||
<View style={styles.infoRow}>
|
||||
<View style={styles.infoItem}>
|
||||
<Text style={styles.infoLabel}>Age</Text>
|
||||
<Text style={styles.infoValue}>{patient.patientdetails.PatAge}</Text>
|
||||
</View>
|
||||
<View style={styles.infoItem}>
|
||||
<Text style={styles.infoLabel}>Sex</Text>
|
||||
<Text style={styles.infoValue}>{patient.patientdetails.PatSex}</Text>
|
||||
</View>
|
||||
<View style={styles.infoItem}>
|
||||
<Text style={styles.infoLabel}>Modality</Text>
|
||||
<Text style={[styles.infoValue, styles.modalityText, {
|
||||
color: getModalityColor(patient.patientdetails.Modality?.substring(1))
|
||||
}]}>
|
||||
{patient.patientdetails.Modality?.substring(1)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.institutionRow}>
|
||||
<CustomIcon name="hospital-building" color={Colors.textPrimary} size={14} />
|
||||
<Text style={styles.institutionText}>
|
||||
{patient.patientdetails.InstName}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.bottomRow}>
|
||||
<Text style={styles.dateText}>
|
||||
{formatDate(patient.created_at)}
|
||||
</Text>
|
||||
<Text style={[styles.statusText, {
|
||||
color: patient.patientdetails.Status === 'Active' ? Colors.success : Colors.textSecondary
|
||||
}]}>
|
||||
{patient.patientdetails.Status}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardFooter}>
|
||||
<Text style={styles.seriesText}>
|
||||
{patient.series.length} Series Available
|
||||
</Text>
|
||||
<IconButton
|
||||
name="chevron-right"
|
||||
color={typeConfig.color}
|
||||
size={20}
|
||||
onPress={() => handlePatientPress(patient)}
|
||||
/>
|
||||
</View>
|
||||
</Card>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
|
||||
return (
|
||||
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
|
||||
<Text style={{color:'#FFFFFF'}} >Dashboard Detail screen</Text>
|
||||
<View style={styles.container}>
|
||||
<CustomHeader
|
||||
title={`${caseType} Cases`}
|
||||
showBackButton={true}
|
||||
// onBackPress={() => navigation.goBack()}
|
||||
/>
|
||||
|
||||
<ScrollView contentContainerStyle={styles.content}>
|
||||
{/* Summary Card */}
|
||||
<InfoCard style={[styles.summaryCard, { backgroundColor: typeConfig.bgColor }]}>
|
||||
<View style={styles.summaryHeader}>
|
||||
<CustomIcon name={typeConfig.icon} color={typeConfig.color} size={28} />
|
||||
<View style={styles.summaryText}>
|
||||
<Text style={styles.summaryTitle}>
|
||||
{caseType} Cases
|
||||
</Text>
|
||||
<Text style={styles.summaryCount}>
|
||||
{filteredPatients.length} Patient{filteredPatients.length !== 1 ? 's' : ''}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</InfoCard>
|
||||
|
||||
{/* Search and Sort */}
|
||||
<View style={styles.controlsContainer}>
|
||||
<SearchInput
|
||||
placeholder={`Search ${caseType.toLowerCase()} cases...`}
|
||||
value={search}
|
||||
onChangeText={setSearch}
|
||||
containerStyle={styles.searchInput}
|
||||
/>
|
||||
|
||||
<View style={styles.sortContainer}>
|
||||
<Text style={styles.sortLabel}>Sort by:</Text>
|
||||
<View style={styles.sortButtons}>
|
||||
{(['date', 'name', 'age'] as const).map(option => (
|
||||
<TouchableOpacity
|
||||
key={option}
|
||||
style={[
|
||||
styles.sortButton,
|
||||
sortBy === option && { backgroundColor: typeConfig.color }
|
||||
]}
|
||||
onPress={() => setSortBy(option)}
|
||||
>
|
||||
<Text style={[
|
||||
styles.sortButtonText,
|
||||
sortBy === option && { color: '#FFFFFF' }
|
||||
]}>
|
||||
{option.charAt(0).toUpperCase() + option.slice(1)}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Patient List */}
|
||||
{filteredPatients.length > 0 ? (
|
||||
filteredPatients.map(renderPatientCard)
|
||||
) : (
|
||||
<Card style={styles.emptyCard}>
|
||||
<CustomIcon name="inbox" color={Colors.textSecondary} size={48} />
|
||||
<Text style={styles.emptyTitle}>
|
||||
No {caseType.toLowerCase()} cases found
|
||||
</Text>
|
||||
<Text style={styles.emptySubtitle}>
|
||||
{search ? 'Try adjusting your search terms' : 'All clear for now!'}
|
||||
</Text>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Patient Detail Modal */}
|
||||
<Modal
|
||||
visible={modalVisible}
|
||||
onRequestClose={() => setModalVisible(false)}
|
||||
>
|
||||
{selectedPatient && (
|
||||
<View style={styles.modalContent}>
|
||||
<Text style={styles.modalTitle}>Patient Details</Text>
|
||||
|
||||
<View style={styles.modalSection}>
|
||||
<Text style={styles.modalSectionTitle}>Basic Information</Text>
|
||||
<Text style={styles.modalText}>Name: {selectedPatient.patientdetails.Name}</Text>
|
||||
<Text style={styles.modalText}>ID: {selectedPatient.patientdetails.PatID}</Text>
|
||||
<Text style={styles.modalText}>Age: {selectedPatient.patientdetails.PatAge}</Text>
|
||||
<Text style={styles.modalText}>Sex: {selectedPatient.patientdetails.PatSex}</Text>
|
||||
<Text style={styles.modalText}>Status: {selectedPatient.patientdetails.Status}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.modalSection}>
|
||||
<Text style={styles.modalSectionTitle}>Medical Information</Text>
|
||||
<Text style={styles.modalText}>Modality: {selectedPatient.patientdetails.Modality}</Text>
|
||||
<Text style={styles.modalText}>Institution: {selectedPatient.patientdetails.InstName}</Text>
|
||||
<Text style={styles.modalText}>Report Status: {selectedPatient.patientdetails.ReportStatus || 'Pending'}</Text>
|
||||
<Text style={styles.modalText}>Case Type: {selectedPatient.type}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.modalSection}>
|
||||
<Text style={styles.modalSectionTitle}>Series Information</Text>
|
||||
<Text style={styles.modalText}>Available Series: {selectedPatient.series.length}</Text>
|
||||
<Text style={styles.modalText}>Created: {formatDate(selectedPatient.created_at)}</Text>
|
||||
<Text style={styles.modalText}>Updated: {formatDate(selectedPatient.updated_at)}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.modalButtons}>
|
||||
<Button
|
||||
title="View Images"
|
||||
onPress={() => {
|
||||
// Navigate to image viewer
|
||||
setModalVisible(false);
|
||||
// navigation.navigate('ImageViewer', { patientId: selectedPatient.id });
|
||||
}}
|
||||
style={[styles.modalButton, { backgroundColor: typeConfig.color }]}
|
||||
/>
|
||||
<Button
|
||||
title="Close"
|
||||
onPress={() => setModalVisible(false)}
|
||||
style={styles.modalButton}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</Modal>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@ -31,8 +365,206 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
content: {
|
||||
padding: Spacing.lg,
|
||||
flex:1
|
||||
}
|
||||
},
|
||||
summaryCard: {
|
||||
marginBottom: Spacing.lg,
|
||||
},
|
||||
summaryHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
summaryText: {
|
||||
marginLeft: Spacing.md,
|
||||
flex: 1,
|
||||
},
|
||||
summaryTitle: {
|
||||
fontFamily: Typography.fontFamily.bold,
|
||||
fontSize: Typography.fontSize.lg,
|
||||
color: Colors.textPrimary,
|
||||
},
|
||||
summaryCount: {
|
||||
fontFamily: Typography.fontFamily.regular,
|
||||
fontSize: Typography.fontSize.md,
|
||||
color: Colors.textSecondary,
|
||||
marginTop: Spacing.xs,
|
||||
},
|
||||
controlsContainer: {
|
||||
marginBottom: Spacing.lg,
|
||||
},
|
||||
searchInput: {
|
||||
marginBottom: Spacing.md,
|
||||
},
|
||||
sortContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
sortLabel: {
|
||||
fontFamily: Typography.fontFamily.regular,
|
||||
fontSize: Typography.fontSize.sm,
|
||||
color: Colors.textSecondary,
|
||||
},
|
||||
sortButtons: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
sortButton: {
|
||||
paddingHorizontal: Spacing.sm,
|
||||
paddingVertical: Spacing.xs,
|
||||
backgroundColor: Colors.cardBackground,
|
||||
borderRadius: 16,
|
||||
marginLeft: Spacing.xs,
|
||||
},
|
||||
sortButtonText: {
|
||||
fontFamily: Typography.fontFamily.regular,
|
||||
fontSize: Typography.fontSize.sm,
|
||||
color: Colors.textSecondary,
|
||||
},
|
||||
patientCard: {
|
||||
marginBottom: Spacing.md,
|
||||
borderLeftWidth: 4,
|
||||
},
|
||||
cardHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
marginBottom: Spacing.sm,
|
||||
},
|
||||
patientInfo: {
|
||||
flex: 1,
|
||||
},
|
||||
patientName: {
|
||||
fontFamily: Typography.fontFamily.bold,
|
||||
fontSize: Typography.fontSize.md,
|
||||
color: Colors.textPrimary,
|
||||
},
|
||||
patientId: {
|
||||
fontFamily: Typography.fontFamily.regular,
|
||||
fontSize: Typography.fontSize.sm,
|
||||
color: Colors.textSecondary,
|
||||
marginTop: Spacing.xs,
|
||||
},
|
||||
urgencyBadge: {
|
||||
padding: Spacing.xs,
|
||||
borderRadius: 12,
|
||||
backgroundColor: 'rgba(0,0,0,0.05)',
|
||||
},
|
||||
cardBody: {
|
||||
marginBottom: Spacing.sm,
|
||||
},
|
||||
infoRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: Spacing.sm,
|
||||
},
|
||||
infoItem: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
},
|
||||
infoLabel: {
|
||||
fontFamily: Typography.fontFamily.regular,
|
||||
fontSize: Typography.fontSize.xs,
|
||||
color: Colors.textSecondary,
|
||||
marginBottom: Spacing.xs,
|
||||
},
|
||||
infoValue: {
|
||||
fontFamily: Typography.fontFamily.bold,
|
||||
fontSize: Typography.fontSize.sm,
|
||||
color: Colors.textPrimary,
|
||||
},
|
||||
modalityText: {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
institutionRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginBottom: Spacing.sm,
|
||||
},
|
||||
institutionText: {
|
||||
fontFamily: Typography.fontFamily.regular,
|
||||
fontSize: Typography.fontSize.sm,
|
||||
color: Colors.textSecondary,
|
||||
marginLeft: Spacing.xs,
|
||||
flex: 1,
|
||||
},
|
||||
bottomRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
dateText: {
|
||||
fontFamily: Typography.fontFamily.regular,
|
||||
fontSize: Typography.fontSize.xs,
|
||||
color: Colors.textSecondary,
|
||||
},
|
||||
statusText: {
|
||||
fontFamily: Typography.fontFamily.bold,
|
||||
fontSize: Typography.fontSize.xs,
|
||||
},
|
||||
cardFooter: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingTop: Spacing.sm,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#E5E5E5',
|
||||
},
|
||||
seriesText: {
|
||||
fontFamily: Typography.fontFamily.regular,
|
||||
fontSize: Typography.fontSize.sm,
|
||||
color: Colors.textSecondary,
|
||||
},
|
||||
emptyCard: {
|
||||
alignItems: 'center',
|
||||
padding: Spacing.xl,
|
||||
marginTop: Spacing.xl,
|
||||
},
|
||||
emptyTitle: {
|
||||
fontFamily: Typography.fontFamily.bold,
|
||||
fontSize: Typography.fontSize.lg,
|
||||
color: Colors.textPrimary,
|
||||
marginTop: Spacing.md,
|
||||
textAlign: 'center',
|
||||
},
|
||||
emptySubtitle: {
|
||||
fontFamily: Typography.fontFamily.regular,
|
||||
fontSize: Typography.fontSize.md,
|
||||
color: Colors.textSecondary,
|
||||
marginTop: Spacing.xs,
|
||||
textAlign: 'center',
|
||||
},
|
||||
modalContent: {
|
||||
maxHeight: '80%',
|
||||
},
|
||||
modalTitle: {
|
||||
fontFamily: Typography.fontFamily.bold,
|
||||
fontSize: Typography.fontSize.lg,
|
||||
color: Colors.primary,
|
||||
marginBottom: Spacing.lg,
|
||||
textAlign: 'center',
|
||||
},
|
||||
modalSection: {
|
||||
marginBottom: Spacing.md,
|
||||
},
|
||||
modalSectionTitle: {
|
||||
fontFamily: Typography.fontFamily.bold,
|
||||
fontSize: Typography.fontSize.md,
|
||||
color: Colors.textPrimary,
|
||||
marginBottom: Spacing.sm,
|
||||
},
|
||||
modalText: {
|
||||
fontFamily: Typography.fontFamily.regular,
|
||||
fontSize: Typography.fontSize.sm,
|
||||
color: Colors.textPrimary,
|
||||
marginBottom: Spacing.xs,
|
||||
},
|
||||
modalButtons: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
marginTop: Spacing.lg,
|
||||
},
|
||||
modalButton: {
|
||||
flex: 0.4,
|
||||
},
|
||||
});
|
||||
|
||||
export default DashBoardDetail;
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
* Copyright (c) Spurrin Innovations. All rights reserved.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
import { View, Text, StyleSheet, ScrollView } from 'react-native';
|
||||
import { Button } from '../../../../shared/src/components/Button';
|
||||
import { Card, InfoCard } from '../../../../shared/src/components/Card';
|
||||
@ -13,8 +13,50 @@ import { TextInput, SearchInput } from '../../../../shared/src/components/Input'
|
||||
import { Modal, ConfirmModal, AlertModal } from '../../../../shared/src/components/Modal';
|
||||
import { Spinner, LoadingOverlay } from '../../../../shared/src/components/Loading';
|
||||
import { CustomIcon, IconButton } from '../../../../shared/src/components/Icons';
|
||||
import { CustomHeader } from '../../../../shared/src/components/Header'; // Import CustomHeader
|
||||
import { CustomHeader } from '../../../../shared/src/components/Header';
|
||||
import { Colors, Spacing, Typography } from '../../../../shared/src/theme';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { fetchPatients, selectPatients } from '../redux';
|
||||
|
||||
interface PatientDetails {
|
||||
Date: string;
|
||||
Name: string;
|
||||
PatID: string;
|
||||
PatAge: string;
|
||||
PatSex: string;
|
||||
Status: string;
|
||||
InstName: string;
|
||||
Modality: 'DX' | 'CT' | 'MR';
|
||||
ReportStatus: string | null;
|
||||
}
|
||||
|
||||
interface Series {
|
||||
Path: string[];
|
||||
SerDes: string;
|
||||
ViePos: string | null;
|
||||
pngpath: string;
|
||||
SeriesNum: string;
|
||||
ImgTotalinSeries: string;
|
||||
}
|
||||
|
||||
export interface MedicalCase {
|
||||
id: number;
|
||||
patientdetails: PatientDetails;
|
||||
series: Series[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
series_id: string | null;
|
||||
type: 'Critical' | 'Routine' | 'Emergency';
|
||||
}
|
||||
|
||||
interface CaseStats {
|
||||
total: number;
|
||||
modalityCounts: {
|
||||
DX: number;
|
||||
CT: number;
|
||||
MR: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* DashboardScreen - The primary landing screen after login, featuring a custom header,
|
||||
@ -27,6 +69,125 @@ const DashboardScreen: React.FC<{ navigation: any }> = ({ navigation }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
const [input, setInput] = useState('');
|
||||
const [selectedCase, setSelectedCase] = useState<MedicalCase | null>(null);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { access_token, display_name } = useSelector((state: any) => state.auth.user);
|
||||
const patientData: MedicalCase[] = useSelector(selectPatients) || [];
|
||||
|
||||
useEffect(() => {
|
||||
//@ts-ignore
|
||||
dispatch(fetchPatients(access_token));
|
||||
}, []);
|
||||
|
||||
// Filter cases based on search
|
||||
const filteredCases = useMemo(() => {
|
||||
if (!search.trim()) return patientData;
|
||||
|
||||
return patientData.filter(case_ =>
|
||||
case_.patientdetails.Name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
case_.patientdetails.PatID.toLowerCase().includes(search.toLowerCase()) ||
|
||||
case_.patientdetails.InstName.toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
}, [patientData, search]);
|
||||
|
||||
// Calculate statistics for each case type
|
||||
const caseStats = useMemo(() => {
|
||||
const stats: Record<string, CaseStats> = {
|
||||
Critical: { total: 0, modalityCounts: { DX: 0, CT: 0, MR: 0 } },
|
||||
Routine: { total: 0, modalityCounts: { DX: 0, CT: 0, MR: 0 } },
|
||||
Emergency: { total: 0, modalityCounts: { DX: 0, CT: 0, MR: 0 } }
|
||||
};
|
||||
|
||||
filteredCases.forEach(case_ => {
|
||||
const type = case_.type;
|
||||
const modality = case_.patientdetails.Modality;
|
||||
|
||||
stats[type].total += 1;
|
||||
stats[type].modalityCounts[modality?.substring(1)] += 1;
|
||||
});
|
||||
|
||||
return stats;
|
||||
}, [filteredCases]);
|
||||
|
||||
const getCaseIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case 'Critical':
|
||||
return { name: 'alert', color: Colors.error };
|
||||
case 'Emergency':
|
||||
return { name: 'alert', color: '#FF8C00' }; // Orange color for emergency
|
||||
case 'Routine':
|
||||
return { name: 'check-circle', color: Colors.success };
|
||||
default:
|
||||
return { name: 'info', color: Colors.primary };
|
||||
}
|
||||
};
|
||||
|
||||
const getCaseColor = (type: string) => {
|
||||
switch (type) {
|
||||
case 'Critical':
|
||||
return Colors.error;
|
||||
case 'Emergency':
|
||||
return '#FF8C00'; // Orange
|
||||
case 'Routine':
|
||||
return Colors.success;
|
||||
default:
|
||||
return Colors.textSecondary;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCasePress = (type: string) => {
|
||||
const casesOfType = filteredCases.filter(case_ => case_.type === type);
|
||||
if (casesOfType.length > 0) {
|
||||
navigation.navigate('DashboardDetailScreen',{caseType:type})
|
||||
// setSelectedCase(casesOfType[0]);
|
||||
// setModalVisible(true);
|
||||
}
|
||||
};
|
||||
|
||||
const renderCaseCard = (type: 'Critical' | 'Routine' | 'Emergency') => {
|
||||
const stats = caseStats[type];
|
||||
const icon = getCaseIcon(type);
|
||||
const color = getCaseColor(type);
|
||||
|
||||
if (stats.total === 0) return null; // Don't render card if no cases of this type
|
||||
console.log('counts data',stats.modalityCounts)
|
||||
return (
|
||||
<Card key={type} style={styles.caseCard}>
|
||||
<View style={styles.cardHeader}>
|
||||
<View style={styles.titleRow}>
|
||||
<CustomIcon name={icon.name} color={icon.color} size={24} />
|
||||
<Text style={styles.cardTitle}>{type} Cases</Text>
|
||||
</View>
|
||||
<Text style={[styles.totalCount, { color }]}>
|
||||
{stats.total} Total
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.modalityContainer}>
|
||||
<Text style={styles.modalityLabel}>By Modality:</Text>
|
||||
<View style={styles.modalityRow}>
|
||||
{(['DX', 'CT', 'MR'] as const).map(modality => (
|
||||
<View key={modality} style={styles.modalityItem}>
|
||||
<Text style={styles.modalityType}>{modality}</Text>
|
||||
<Text style={[styles.modalityCount, { color }]}>
|
||||
{stats.modalityCounts[modality]}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
title={`View ${type} Cases`}
|
||||
onPress={() => handleCasePress(type)}
|
||||
style={[styles.button, { backgroundColor: color }]}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
console.log('patients data', patientData);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
@ -35,70 +196,90 @@ const DashboardScreen: React.FC<{ navigation: any }> = ({ navigation }) => {
|
||||
showBackButton={false}
|
||||
/>
|
||||
<ScrollView contentContainerStyle={styles.content}>
|
||||
<SearchInput
|
||||
{/* <SearchInput
|
||||
placeholder="Search cases..."
|
||||
value={search}
|
||||
onChangeText={setSearch}
|
||||
containerStyle={styles.search}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<InfoCard>
|
||||
<Text style={styles.infoTitle}>Welcome, Dr. Smith</Text>
|
||||
<Text style={styles.infoTitle}>Welcome, Dr. {display_name}</Text>
|
||||
<Text style={styles.infoText}>
|
||||
On-Call Status: <Text style={styles.active}>ACTIVE</Text>
|
||||
</Text>
|
||||
<Text style={styles.infoText}>
|
||||
Total Cases: <Text style={styles.active}>{filteredCases.length}</Text>
|
||||
</Text>
|
||||
</InfoCard>
|
||||
|
||||
{/* Dynamic Case Cards */}
|
||||
{(['Critical', 'Emergency', 'Routine'] as const).map(type =>
|
||||
renderCaseCard(type)
|
||||
)}
|
||||
|
||||
{filteredCases.length === 0 && (
|
||||
<Card>
|
||||
<Text style={styles.cardTitle}>Critical Cases</Text>
|
||||
<View style={styles.row}>
|
||||
<CustomIcon name="alert" color={Colors.error} size={24} />
|
||||
<Text style={styles.criticalText}>Bed 3 - Hemorrhage (93% AI)</Text>
|
||||
<Button
|
||||
title="Review Now"
|
||||
onPress={() => setModalVisible(true)}
|
||||
style={styles.button}
|
||||
/>
|
||||
</View>
|
||||
</Card>
|
||||
<Card>
|
||||
<Text style={styles.cardTitle}>Routine Cases</Text>
|
||||
<View style={styles.row}>
|
||||
<CustomIcon name="check-circle" color={Colors.success} size={24} />
|
||||
<Text style={styles.routineText}>Bed 12 - Headache (99% AI)</Text>
|
||||
<IconButton
|
||||
name="chevron-right"
|
||||
onPress={() => setConfirmVisible(true)}
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.noDataText}>
|
||||
{search ? 'No cases found matching your search.' : 'No cases available.'}
|
||||
</Text>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<TextInput
|
||||
placeholder="Add note..."
|
||||
value={input}
|
||||
onChangeText={setInput}
|
||||
style={styles.input}
|
||||
/>
|
||||
<Button
|
||||
title="Show Alert"
|
||||
onPress={() => setAlertVisible(true)}
|
||||
style={styles.button}
|
||||
/>
|
||||
<Button
|
||||
title={loading ? 'Loading...' : 'Show Loading Overlay'}
|
||||
onPress={() => setLoading(true)}
|
||||
style={styles.button}
|
||||
/>
|
||||
{/* <Spinner style={styles.spinner} /> */}
|
||||
|
||||
{/* Modals */}
|
||||
<Modal
|
||||
visible={modalVisible}
|
||||
onRequestClose={() => setModalVisible(false)}>
|
||||
<Text style={styles.modalTitle}>Critical Case Details</Text>
|
||||
<Text>Patient: John Doe, 45M</Text>
|
||||
{selectedCase && (
|
||||
<>
|
||||
<Text style={styles.modalTitle}>
|
||||
{selectedCase.type} Case Details
|
||||
</Text>
|
||||
<View style={styles.modalContent}>
|
||||
<Text style={styles.modalText}>
|
||||
Patient: {selectedCase.patientdetails.Name}
|
||||
</Text>
|
||||
<Text style={styles.modalText}>
|
||||
ID: {selectedCase.patientdetails.PatID}
|
||||
</Text>
|
||||
<Text style={styles.modalText}>
|
||||
Age: {selectedCase.patientdetails.PatAge}
|
||||
</Text>
|
||||
<Text style={styles.modalText}>
|
||||
Sex: {selectedCase.patientdetails.PatSex}
|
||||
</Text>
|
||||
<Text style={styles.modalText}>
|
||||
Modality: {selectedCase.patientdetails.Modality}
|
||||
</Text>
|
||||
<Text style={styles.modalText}>
|
||||
Institution: {selectedCase.patientdetails.InstName}
|
||||
</Text>
|
||||
<Text style={styles.modalText}>
|
||||
Status: {selectedCase.patientdetails.Status}
|
||||
</Text>
|
||||
<Text style={styles.modalText}>
|
||||
Series: {selectedCase.series.length} available
|
||||
</Text>
|
||||
</View>
|
||||
<Button
|
||||
title="Close"
|
||||
onPress={() => setModalVisible(false)}
|
||||
onPress={() => {
|
||||
setModalVisible(false);
|
||||
setSelectedCase(null);
|
||||
}}
|
||||
style={styles.button}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
<ConfirmModal
|
||||
visible={confirmVisible}
|
||||
title="Confirm Action"
|
||||
@ -108,6 +289,7 @@ const DashboardScreen: React.FC<{ navigation: any }> = ({ navigation }) => {
|
||||
}}
|
||||
onCancel={() => setConfirmVisible(false)}
|
||||
/>
|
||||
|
||||
<AlertModal
|
||||
visible={alertVisible}
|
||||
title="Critical Alert"
|
||||
@ -116,6 +298,7 @@ const DashboardScreen: React.FC<{ navigation: any }> = ({ navigation }) => {
|
||||
iconColor={Colors.error}
|
||||
onDismiss={() => setAlertVisible(false)}
|
||||
/>
|
||||
|
||||
<LoadingOverlay visible={loading} />
|
||||
</ScrollView>
|
||||
</View>
|
||||
@ -143,43 +326,74 @@ const styles = StyleSheet.create({
|
||||
fontFamily: Typography.fontFamily.regular,
|
||||
fontSize: Typography.fontSize.md,
|
||||
color: Colors.textSecondary,
|
||||
marginBottom: Spacing.xs,
|
||||
},
|
||||
active: {
|
||||
color: Colors.success,
|
||||
fontWeight: 'bold',
|
||||
fontFamily: Typography.fontFamily.bold,
|
||||
},
|
||||
caseCard: {
|
||||
marginBottom: Spacing.md,
|
||||
},
|
||||
cardHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: Spacing.md,
|
||||
},
|
||||
titleRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
cardTitle: {
|
||||
fontFamily: Typography.fontFamily.bold,
|
||||
fontSize: Typography.fontSize.md,
|
||||
color: Colors.textPrimary,
|
||||
marginBottom: Spacing.sm,
|
||||
marginLeft: Spacing.sm,
|
||||
},
|
||||
row: {
|
||||
totalCount: {
|
||||
fontFamily: Typography.fontFamily.bold,
|
||||
fontSize: Typography.fontSize.lg,
|
||||
},
|
||||
modalityContainer: {
|
||||
marginBottom: Spacing.md,
|
||||
},
|
||||
modalityLabel: {
|
||||
fontFamily: Typography.fontFamily.regular,
|
||||
fontSize: Typography.fontSize.sm,
|
||||
color: Colors.textSecondary,
|
||||
marginBottom: Spacing.xs,
|
||||
},
|
||||
modalityRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
},
|
||||
modalityItem: {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: Spacing.sm,
|
||||
},
|
||||
criticalText: {
|
||||
color: Colors.error,
|
||||
flex: 1,
|
||||
marginLeft: Spacing.sm,
|
||||
fontFamily: Typography.fontFamily.regular,
|
||||
},
|
||||
routineText: {
|
||||
color: Colors.success,
|
||||
flex: 1,
|
||||
marginLeft: Spacing.sm,
|
||||
modalityType: {
|
||||
fontFamily: Typography.fontFamily.regular,
|
||||
fontSize: Typography.fontSize.sm,
|
||||
color: Colors.textSecondary,
|
||||
},
|
||||
modalityCount: {
|
||||
fontFamily: Typography.fontFamily.bold,
|
||||
fontSize: Typography.fontSize.md,
|
||||
marginTop: Spacing.xs,
|
||||
},
|
||||
button: {
|
||||
marginLeft: Spacing.sm,
|
||||
marginTop: Spacing.sm,
|
||||
},
|
||||
input: {
|
||||
marginVertical: Spacing.md,
|
||||
},
|
||||
spinner: {
|
||||
marginVertical: Spacing.md,
|
||||
noDataText: {
|
||||
fontFamily: Typography.fontFamily.regular,
|
||||
fontSize: Typography.fontSize.md,
|
||||
color: Colors.textSecondary,
|
||||
textAlign: 'center',
|
||||
padding: Spacing.lg,
|
||||
},
|
||||
modalTitle: {
|
||||
fontFamily: Typography.fontFamily.bold,
|
||||
@ -188,6 +402,15 @@ const styles = StyleSheet.create({
|
||||
marginBottom: Spacing.md,
|
||||
textAlign: 'center',
|
||||
},
|
||||
modalContent: {
|
||||
marginBottom: Spacing.md,
|
||||
},
|
||||
modalText: {
|
||||
fontFamily: Typography.fontFamily.regular,
|
||||
fontSize: Typography.fontSize.md,
|
||||
color: Colors.textPrimary,
|
||||
marginBottom: Spacing.xs,
|
||||
},
|
||||
});
|
||||
|
||||
export default DashboardScreen;
|
||||
|
||||
@ -21,6 +21,11 @@ const api = create({
|
||||
|
||||
export const caseAPI = {
|
||||
getCases: (token:string) => api.get('/api/dicom/medpacks-sync/get-synced-medpacks-data',{},buildHeaders({token})),
|
||||
// get patients data
|
||||
getPatients: (token:string) =>{
|
||||
console.log('token',token)
|
||||
return api.get('/api/dicom/medpacks-sync/get-synced-medpacks-data',{},buildHeaders({token}))
|
||||
}
|
||||
// Add more endpoints as needed
|
||||
};
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import { ProfileScreen, SettingsScreen, PreferencesScreen } from '../screens';
|
||||
import { Colors } from '../../../../shared/src/theme';
|
||||
|
||||
export type ProfileStackParamList = {
|
||||
Profile: undefined;
|
||||
ProfileScreen: undefined;
|
||||
Settings: undefined;
|
||||
Preferences: undefined;
|
||||
};
|
||||
@ -23,13 +23,13 @@ const Stack = createStackNavigator<ProfileStackParamList>();
|
||||
*/
|
||||
const ProfileNavigator: React.FC = () => (
|
||||
<Stack.Navigator
|
||||
initialRouteName="Profile"
|
||||
initialRouteName="ProfileScreen"
|
||||
screenOptions={{
|
||||
headerStyle: { backgroundColor: Colors.primary },
|
||||
}}
|
||||
>
|
||||
<Stack.Screen
|
||||
name="Profile"
|
||||
name="ProfileScreen"
|
||||
component={ProfileScreen}
|
||||
options={{ title: 'Profile',headerShown:false }}
|
||||
/>
|
||||
|
||||
@ -202,7 +202,10 @@ const ProfileScreen: React.FC<{ navigation: any }> = ({ navigation }) => {
|
||||
/>
|
||||
<TouchableOpacity
|
||||
style={styles.editIconContainer}
|
||||
onPress={() => handleEdit('Profile Photo', user.profile_photo_url || '')}>
|
||||
// onPress={() =>
|
||||
// handleEdit('Profile Photo', user.profile_photo_url || '')
|
||||
// }
|
||||
>
|
||||
<Icon name="edit-2" size={16} color={Colors.background} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
@ -11,6 +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 */; };
|
||||
2B63C139D1C7487D854E5414 /* Roboto-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0E209063CB8A4F2A9B5F73FC /* Roboto-Black.ttf */; };
|
||||
E2F63E44EA94431C9697867A /* Roboto-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 33A1869F0A6846C980E48C0A /* Roboto-Bold.ttf */; };
|
||||
F86F120BE48D4CFF9916459C /* Roboto-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3C216A8717FF4CE8811E0980 /* Roboto-ExtraBold.ttf */; };
|
||||
CE2ADC3F43A3462DAAB5A976 /* Roboto-ExtraLight.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 2BFC5F3B38744854821CB455 /* Roboto-ExtraLight.ttf */; };
|
||||
60FAFE61E4C54472B6A3B3D1 /* Roboto-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A583FF188F1E46B98CDC84F2 /* Roboto-Light.ttf */; };
|
||||
4EE41339FC4D437C8731CECE /* Roboto-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 78E14701D8014CB08C8BB0F3 /* Roboto-Medium.ttf */; };
|
||||
92D1CEAF3AB14A22A0261A5F /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17E4616E779A4CCCBEF89C5E /* Roboto-Regular.ttf */; };
|
||||
A6058591ADBE4B1FAA793275 /* Roboto-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3E358D5508C340C2826B03DA /* Roboto-SemiBold.ttf */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -35,6 +43,14 @@
|
||||
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = NeoScan_Radiologist/AppDelegate.swift; sourceTree = "<group>"; };
|
||||
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = NeoScan_Radiologist/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
||||
0E209063CB8A4F2A9B5F73FC /* Roboto-Black.ttf */ = {isa = PBXFileReference; name = "Roboto-Black.ttf"; path = "../shared/src/assets/fonts/Roboto-Black.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
||||
33A1869F0A6846C980E48C0A /* Roboto-Bold.ttf */ = {isa = PBXFileReference; name = "Roboto-Bold.ttf"; path = "../shared/src/assets/fonts/Roboto-Bold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
||||
3C216A8717FF4CE8811E0980 /* Roboto-ExtraBold.ttf */ = {isa = PBXFileReference; name = "Roboto-ExtraBold.ttf"; path = "../shared/src/assets/fonts/Roboto-ExtraBold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
||||
2BFC5F3B38744854821CB455 /* Roboto-ExtraLight.ttf */ = {isa = PBXFileReference; name = "Roboto-ExtraLight.ttf"; path = "../shared/src/assets/fonts/Roboto-ExtraLight.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
||||
A583FF188F1E46B98CDC84F2 /* Roboto-Light.ttf */ = {isa = PBXFileReference; name = "Roboto-Light.ttf"; path = "../shared/src/assets/fonts/Roboto-Light.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
||||
78E14701D8014CB08C8BB0F3 /* Roboto-Medium.ttf */ = {isa = PBXFileReference; name = "Roboto-Medium.ttf"; path = "../shared/src/assets/fonts/Roboto-Medium.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
||||
17E4616E779A4CCCBEF89C5E /* Roboto-Regular.ttf */ = {isa = PBXFileReference; name = "Roboto-Regular.ttf"; path = "../shared/src/assets/fonts/Roboto-Regular.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
||||
3E358D5508C340C2826B03DA /* Roboto-SemiBold.ttf */ = {isa = PBXFileReference; name = "Roboto-SemiBold.ttf"; path = "../shared/src/assets/fonts/Roboto-SemiBold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -93,6 +109,7 @@
|
||||
83CBBA001A601CBA00E9B192 /* Products */,
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */,
|
||||
BBD78D7AC51CEA395F1C20DB /* Pods */,
|
||||
29546F9717134CF38C4927C5 /* Resources */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
@ -116,6 +133,22 @@
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
29546F9717134CF38C4927C5 /* Resources */ = {
|
||||
isa = "PBXGroup";
|
||||
children = (
|
||||
0E209063CB8A4F2A9B5F73FC /* Roboto-Black.ttf */,
|
||||
33A1869F0A6846C980E48C0A /* Roboto-Bold.ttf */,
|
||||
3C216A8717FF4CE8811E0980 /* Roboto-ExtraBold.ttf */,
|
||||
2BFC5F3B38744854821CB455 /* Roboto-ExtraLight.ttf */,
|
||||
A583FF188F1E46B98CDC84F2 /* Roboto-Light.ttf */,
|
||||
78E14701D8014CB08C8BB0F3 /* Roboto-Medium.ttf */,
|
||||
17E4616E779A4CCCBEF89C5E /* Roboto-Regular.ttf */,
|
||||
3E358D5508C340C2826B03DA /* Roboto-SemiBold.ttf */,
|
||||
);
|
||||
name = Resources;
|
||||
sourceTree = "<group>";
|
||||
path = "";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@ -185,6 +218,14 @@
|
||||
files = (
|
||||
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||
2B63C139D1C7487D854E5414 /* Roboto-Black.ttf in Resources */,
|
||||
E2F63E44EA94431C9697867A /* Roboto-Bold.ttf in Resources */,
|
||||
F86F120BE48D4CFF9916459C /* Roboto-ExtraBold.ttf in Resources */,
|
||||
CE2ADC3F43A3462DAAB5A976 /* Roboto-ExtraLight.ttf in Resources */,
|
||||
60FAFE61E4C54472B6A3B3D1 /* Roboto-Light.ttf in Resources */,
|
||||
4EE41339FC4D437C8731CECE /* Roboto-Medium.ttf in Resources */,
|
||||
92D1CEAF3AB14A22A0261A5F /* Roboto-Regular.ttf in Resources */,
|
||||
A6058591ADBE4B1FAA793275 /* Roboto-SemiBold.ttf in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@ -26,14 +26,13 @@
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<!-- Do not change NSAllowsArbitraryLoads to true, or you will risk app rejection! -->
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<false/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<string/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
@ -48,5 +47,16 @@
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<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>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
37
ios/link-assets-manifest.json
Normal file
37
ios/link-assets-manifest.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"migIndex": 1,
|
||||
"data": [
|
||||
{
|
||||
"path": "shared/src/assets/fonts/Roboto-Black.ttf",
|
||||
"sha1": "d1678489a8d5645f16486ec52d77b651ff0bf327"
|
||||
},
|
||||
{
|
||||
"path": "shared/src/assets/fonts/Roboto-Bold.ttf",
|
||||
"sha1": "508c35dee818addce6cc6d1fb6e42f039da5a7cf"
|
||||
},
|
||||
{
|
||||
"path": "shared/src/assets/fonts/Roboto-ExtraBold.ttf",
|
||||
"sha1": "3dbfd71b6fbcfbd8e7ee8a8dd033dc5aaad63249"
|
||||
},
|
||||
{
|
||||
"path": "shared/src/assets/fonts/Roboto-ExtraLight.ttf",
|
||||
"sha1": "df556e64732e5c272349e13cb5f87591a1ae779b"
|
||||
},
|
||||
{
|
||||
"path": "shared/src/assets/fonts/Roboto-Light.ttf",
|
||||
"sha1": "318b44c0a32848f78bf11d4fbf3355d00647a796"
|
||||
},
|
||||
{
|
||||
"path": "shared/src/assets/fonts/Roboto-Medium.ttf",
|
||||
"sha1": "fa5192203f85ddb667579e1bdf26f12098bb873b"
|
||||
},
|
||||
{
|
||||
"path": "shared/src/assets/fonts/Roboto-Regular.ttf",
|
||||
"sha1": "3bff51436aa7eb995d84cfc592cc63e1316bb400"
|
||||
},
|
||||
{
|
||||
"path": "shared/src/assets/fonts/Roboto-SemiBold.ttf",
|
||||
"sha1": "9ca139684fe902c8310dd82991648376ac9838db"
|
||||
}
|
||||
]
|
||||
}
|
||||
7
react-native.config.js
Normal file
7
react-native.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
project: {
|
||||
ios: {},
|
||||
android: {},
|
||||
},
|
||||
assets: ['./shared/src/assets/fonts'], // adjust according to your path
|
||||
};
|
||||
BIN
shared/src/assets/fonts/Roboto-Black.ttf
Normal file
BIN
shared/src/assets/fonts/Roboto-Black.ttf
Normal file
Binary file not shown.
BIN
shared/src/assets/fonts/Roboto-Bold.ttf
Normal file
BIN
shared/src/assets/fonts/Roboto-Bold.ttf
Normal file
Binary file not shown.
BIN
shared/src/assets/fonts/Roboto-ExtraBold.ttf
Normal file
BIN
shared/src/assets/fonts/Roboto-ExtraBold.ttf
Normal file
Binary file not shown.
BIN
shared/src/assets/fonts/Roboto-ExtraLight.ttf
Normal file
BIN
shared/src/assets/fonts/Roboto-ExtraLight.ttf
Normal file
Binary file not shown.
BIN
shared/src/assets/fonts/Roboto-Light.ttf
Normal file
BIN
shared/src/assets/fonts/Roboto-Light.ttf
Normal file
Binary file not shown.
BIN
shared/src/assets/fonts/Roboto-Medium.ttf
Normal file
BIN
shared/src/assets/fonts/Roboto-Medium.ttf
Normal file
Binary file not shown.
BIN
shared/src/assets/fonts/Roboto-Regular.ttf
Normal file
BIN
shared/src/assets/fonts/Roboto-Regular.ttf
Normal file
Binary file not shown.
BIN
shared/src/assets/fonts/Roboto-SemiBold.ttf
Normal file
BIN
shared/src/assets/fonts/Roboto-SemiBold.ttf
Normal file
Binary file not shown.
@ -51,7 +51,7 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
text: {
|
||||
color: Colors.background,
|
||||
fontSize: Typography.fontSize.md,
|
||||
fontSize: Typography.fontSize.sm,
|
||||
fontFamily: Typography.fontFamily.bold,
|
||||
},
|
||||
disabled: {
|
||||
|
||||
@ -11,7 +11,7 @@ import { cardStyles } from './Card.styles';
|
||||
|
||||
interface CardProps {
|
||||
children: ReactNode;
|
||||
style?: ViewStyle;
|
||||
style?: ViewStyle | [ViewStyle,{}];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -11,7 +11,7 @@ import { cardStyles } from './Card.styles';
|
||||
|
||||
interface InfoCardProps {
|
||||
children: ReactNode;
|
||||
style?: ViewStyle;
|
||||
style?: ViewStyle |[ViewStyle,{}];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -8,8 +8,12 @@
|
||||
// Typography system for the Radiologist App
|
||||
export const Typography = {
|
||||
fontFamily: {
|
||||
regular: 'Roboto-Regular',
|
||||
bold:'Roboto-Bold',
|
||||
exraBold:'Roboto-ExtraBold',
|
||||
medium:'Roboto-Medium',
|
||||
light:'Roboto-Light',
|
||||
regular:'Roboto-Regular',
|
||||
semiBold:'Roboto-SemiBold'
|
||||
},
|
||||
fontSize: {
|
||||
xs: 12,
|
||||
|
||||
30
shared/src/utils/types/Case.ts
Normal file
30
shared/src/utils/types/Case.ts
Normal file
@ -0,0 +1,30 @@
|
||||
interface PatientDetails {
|
||||
Date: string;
|
||||
Name: string;
|
||||
PatID: string;
|
||||
PatAge: string;
|
||||
PatSex: string;
|
||||
Status: string;
|
||||
InstName: string;
|
||||
Modality: 'DX'|'CT'|'MR';
|
||||
ReportStatus: string | null;
|
||||
}
|
||||
|
||||
interface Series {
|
||||
Path: string[];
|
||||
SerDes: string;
|
||||
ViePos: string | null;
|
||||
pngpath: string;
|
||||
SeriesNum: string;
|
||||
ImgTotalinSeries: string;
|
||||
}
|
||||
|
||||
export interface MedicalCase {
|
||||
id: number;
|
||||
patientdetails: PatientDetails;
|
||||
series: Series[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
series_id: string | null;
|
||||
type:'Critical'|'Routine'|'Emergency'
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user