NeoScan_Physician/app/modules/Dashboard/screens/DashBoardDetail.tsx
2025-07-24 20:06:12 +05:30

577 lines
17 KiB
TypeScript

/*
* File: DashboardScreen.tsx
* Description: Sample dashboard screen using Clinical Blue Interface and shared components
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useState, useMemo } from 'react';
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Dimensions } 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';
};
};
}
/**
* DashBoardDetail - Shows detailed list of patients for a specific case type
*/
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 (
<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>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.background,
},
content: {
padding: Spacing.md,
},
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%',
width:Dimensions.get('window').width-100
},
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;
/*
* End of File: DashboardScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/