NeoScan_Physician/app/modules/Dashboard/screens/DashboardScreen.tsx
2025-07-22 17:36:29 +05:30

422 lines
12 KiB
TypeScript

/*
* File: DashboardScreen.tsx
* Description: Main dashboard screen with a custom header and themed components.
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
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';
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 { 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,
* case summaries, and navigation to other parts of the app.
*/
const DashboardScreen: React.FC<{ navigation: any }> = ({ navigation }) => {
const [modalVisible, setModalVisible] = useState(false);
const [confirmVisible, setConfirmVisible] = useState(false);
const [alertVisible, setAlertVisible] = useState(false);
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}>
<CustomHeader
title="Dashboard"
showBackButton={false}
/>
<ScrollView contentContainerStyle={styles.content}>
{/* <SearchInput
placeholder="Search cases..."
value={search}
onChangeText={setSearch}
containerStyle={styles.search}
/> */}
<InfoCard>
<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.noDataText}>
{search ? 'No cases found matching your search.' : 'No cases available.'}
</Text>
</Card>
)}
<TextInput
placeholder="Add note..."
value={input}
onChangeText={setInput}
style={styles.input}
/>
{/* Modals */}
<Modal
visible={modalVisible}
onRequestClose={() => setModalVisible(false)}>
{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);
setSelectedCase(null);
}}
style={styles.button}
/>
</>
)}
</Modal>
<ConfirmModal
visible={confirmVisible}
title="Confirm Action"
message="Are you sure you want to proceed?"
onConfirm={() => {
setConfirmVisible(false);
}}
onCancel={() => setConfirmVisible(false)}
/>
<AlertModal
visible={alertVisible}
title="Critical Alert"
message="Acute Subdural Hemorrhage detected!"
iconName="alert-circle"
iconColor={Colors.error}
onDismiss={() => setAlertVisible(false)}
/>
<LoadingOverlay visible={loading} />
</ScrollView>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.background,
},
content: {
padding: Spacing.lg,
},
search: {
marginBottom: Spacing.md,
},
infoTitle: {
fontFamily: Typography.fontFamily.bold,
fontSize: Typography.fontSize.lg,
color: Colors.textPrimary,
marginBottom: Spacing.xs,
},
infoText: {
fontFamily: Typography.fontFamily.regular,
fontSize: Typography.fontSize.md,
color: Colors.textSecondary,
marginBottom: Spacing.xs,
},
active: {
color: Colors.success,
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,
marginLeft: Spacing.sm,
},
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',
flex: 1,
},
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: {
marginTop: Spacing.sm,
},
input: {
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,
fontSize: Typography.fontSize.lg,
color: Colors.primary,
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;
/*
* End of File: DashboardScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/