added patient detail screen

This commit is contained in:
yashwin-foxy 2025-08-12 18:50:19 +05:30
parent 80a1688e19
commit 413a1d74de
55 changed files with 2512 additions and 425 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"
}
]
}

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,11 +13,13 @@ import {
ScrollView,
TextInput,
TouchableOpacity,
TouchableWithoutFeedback,
Alert,
SafeAreaView,
StatusBar,
KeyboardAvoidingView,
Platform,
Dimensions,
} from 'react-native';
import DateTimePicker from '@react-native-community/datetimepicker';
import Icon from 'react-native-vector-icons/Feather';
@ -36,6 +38,9 @@ import { selectUser } from '../../Auth/redux/authSelectors';
import type { AIPredictionDetailsScreenProps } from '../navigation/navigationTypes';
import type { AIPredictionCase } from '../types';
// Get screen dimensions
const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
// ============================================================================
// ENUMS & TYPES
// ============================================================================
@ -118,19 +123,17 @@ interface APIResponse {
// ============================================================================
const SUGGESTION_TYPE_OPTIONS = [
{ label: 'Treatment', value: SuggestionType.TREATMENT },
{ label: 'Follow Up', value: SuggestionType.FOLLOW_UP },
{ label: 'Diagnosis', value: SuggestionType.DIAGNOSIS },
{ label: 'Other', value: SuggestionType.OTHER },
{ label: 'Treatment', value: SuggestionType.TREATMENT, icon: 'activity', color: '#4CAF50' },
{ label: 'Follow Up', value: SuggestionType.FOLLOW_UP, icon: 'calendar', color: '#2196F3' },
{ label: 'Diagnosis', value: SuggestionType.DIAGNOSIS, icon: 'search', color: '#FF9800' },
{ label: 'Other', value: SuggestionType.OTHER, icon: 'more-horizontal', color: '#9C27B0' },
];
const PRIORITY_OPTIONS = [
{ label: 'Low', value: Priority.LOW, color: theme.colors.success },
{ label: 'Medium', value: Priority.MEDIUM, color: theme.colors.warning },
{ label: 'High', value: Priority.HIGH, color: '#FF5722' },
{ label: 'Critical', value: Priority.CRITICAL, color: theme.colors.critical },
{ label: 'Low', value: Priority.LOW, color: '#4CAF50', bgColor: '#E8F5E8' },
{ label: 'Medium', value: Priority.MEDIUM, color: '#FF9800', bgColor: '#FFF3E0' },
{ label: 'High', value: Priority.HIGH, color: '#FF5722', bgColor: '#FFEBEE' },
{ label: 'Critical', value: Priority.CRITICAL, color: '#F44336', bgColor: '#FFEBEE' },
];
// ============================================================================
@ -150,6 +153,7 @@ const PRIORITY_OPTIONS = [
* - Date picker for expiration
* - Dropdown selections for types and priorities
* - Responsive design with keyboard handling
* - Enhanced visual design with modern mobile styling
*/
const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
navigation,
@ -201,6 +205,7 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
const [showSuccessModal, setShowSuccessModal] = useState(false);
const [apiResponse, setApiResponse] = useState<any>(null);
// ============================================================================
// EFFECTS
// ============================================================================
@ -246,6 +251,14 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
setShowPriorityDropdown(false);
}, []);
/**
* Close all dropdowns
*/
const closeAllDropdowns = useCallback(() => {
setShowSuggestionTypeDropdown(false);
setShowPriorityDropdown(false);
}, []);
/**
* Handle date selection
*/
@ -408,15 +421,33 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
navigation.goBack();
}, [navigation]);
// ============================================================================
// RENDER FUNCTIONS
// ============================================================================
/**
* Render dropdown options
* Render enhanced header
*/
const renderHeader = () => (
<View style={styles.header}>
<TouchableOpacity style={styles.backButton} onPress={handleGoBack}>
<Icon name="arrow-left" size={24} color={theme.colors.primary} />
</TouchableOpacity>
<View style={styles.headerContent}>
<Text style={styles.headerTitle}>AI Prediction Details</Text>
<Text style={styles.headerSubtitle}>Create Medical Suggestion</Text>
</View>
<View style={styles.headerSpacer} />
</View>
);
/**
* Render enhanced dropdown options
*/
const renderDropdownOptions = useCallback((
options: Array<{ label: string; value: any; color?: string }>,
options: Array<{ label: string; value: any; color?: string; bgColor?: string; icon?: string }>,
currentValue: any,
onSelect: (value: any) => void
) => (
@ -430,6 +461,15 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
]}
onPress={() => onSelect(option.value)}
>
<View style={styles.dropdownOptionContent}>
{option.icon && (
<Icon
name={option.icon as any}
size={16}
color={option.color || theme.colors.textSecondary}
style={styles.dropdownOptionIcon}
/>
)}
<Text style={[
styles.dropdownOptionText,
currentValue === option.value && styles.dropdownOptionTextSelected,
@ -437,6 +477,7 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
]}>
{option.label}
</Text>
</View>
{currentValue === option.value && (
<Icon name="check" size={16} color={theme.colors.primary} />
)}
@ -446,27 +487,33 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
), []);
/**
* Render related findings section
* Render enhanced related findings section
*/
const renderRelatedFindings = useCallback(() => (
<View style={styles.relatedFindingsContainer}>
<View style={styles.sectionHeader}>
<Icon name="link" size={20} color={theme.colors.primary} />
<Text style={styles.sectionTitle}>Related Findings</Text>
</View>
<View style={styles.relatedFindingsContent}>
{formData.relatedFindings.map((finding, index) => (
<View key={index} style={styles.relatedFindingItem}>
<View style={styles.relatedFindingInputs}>
<TextInput
style={[styles.input, styles.relatedFindingKey]}
value={finding.key}
onChangeText={(value) => handleUpdateRelatedFinding(index, 'key', value)}
placeholder="Key"
placeholder="Finding Key"
placeholderTextColor={theme.colors.textMuted}
/>
<TextInput
style={[styles.input, styles.relatedFindingValue]}
value={finding.value}
onChangeText={(value) => handleUpdateRelatedFinding(index, 'value', value)}
placeholder="Value"
placeholder="Finding Value"
placeholderTextColor={theme.colors.textMuted}
/>
</View>
<TouchableOpacity
style={styles.removeButton}
onPress={() => handleRemoveRelatedFinding(index)}
@ -481,9 +528,11 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
style={styles.addButton}
onPress={handleAddRelatedFinding}
>
<Text style={styles.addButtonText}>Add</Text>
<Icon name="plus" size={20} color={theme.colors.background} />
<Text style={styles.addButtonText}>Add Finding</Text>
</TouchableOpacity>
</View>
</View>
), [formData.relatedFindings, handleUpdateRelatedFinding, handleRemoveRelatedFinding, handleAddRelatedFinding]);
// ============================================================================
@ -494,8 +543,12 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor={theme.colors.background} />
{/* Header */}
<TouchableWithoutFeedback onPress={closeAllDropdowns}>
<>
{/* Enhanced Header */}
{renderHeader()}
<View style={styles.mainContent}>
<KeyboardAvoidingView
style={styles.keyboardAvoidingView}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
@ -508,7 +561,12 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
>
{/* Patient ID */}
{/* Patient ID Card */}
<View style={styles.patientCard}>
<View style={styles.patientCardHeader}>
<Icon name="user" size={24} color={theme.colors.primary} />
<Text style={styles.patientCardTitle}>Patient Information</Text>
</View>
<View style={styles.formGroup}>
<Text style={styles.label}>Patient ID</Text>
<TextInput
@ -519,16 +577,29 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
placeholderTextColor={theme.colors.textMuted}
/>
</View>
</View>
{/* Suggestion Type */}
{/* Suggestion Type Card */}
<View style={[styles.formCard, {zIndex:1}]}>
<View style={styles.cardHeader}>
<Icon name="edit-3" size={20} color={theme.colors.primary} />
<Text style={styles.cardTitle}>Suggestion Type</Text>
</View>
<View style={styles.formGroup}>
<Text style={styles.label}>Suggestion Type</Text>
<Text style={styles.label}>Type</Text>
<View style={styles.dropdownContainer}>
<TouchableOpacity
style={styles.dropdown}
style={styles.enhancedDropdown}
onPress={() => setShowSuggestionTypeDropdown(!showSuggestionTypeDropdown)}
>
<View style={styles.dropdownContent}>
<Icon
name={SUGGESTION_TYPE_OPTIONS.find(opt => opt.value === formData.suggestionType)?.icon as any || 'edit-3'}
size={18}
color={SUGGESTION_TYPE_OPTIONS.find(opt => opt.value === formData.suggestionType)?.color || theme.colors.primary}
/>
<Text style={styles.dropdownText}>{formData.suggestionType}</Text>
</View>
<Icon name="chevron-down" size={20} color={theme.colors.textSecondary} />
</TouchableOpacity>
{showSuggestionTypeDropdown && renderDropdownOptions(
@ -538,12 +609,20 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
)}
</View>
</View>
</View>
{/* Main Suggestion Card */}
<View style={styles.formCard}>
<View style={styles.cardHeader}>
<Icon name="file-text" size={20} color={theme.colors.primary} />
<Text style={styles.cardTitle}>Suggestion Details</Text>
</View>
{/* Title */}
<View style={styles.formGroup}>
<Text style={styles.label}>Title</Text>
<TextInput
style={styles.input}
style={styles.enhancedInput}
value={formData.title}
onChangeText={(value) => handleFieldChange('title', value)}
placeholder="Recommended CT Scan"
@ -555,7 +634,7 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
<View style={styles.formGroup}>
<Text style={styles.label}>Suggestion Text</Text>
<TextInput
style={[styles.input, styles.textArea]}
style={[styles.enhancedInput, styles.textArea]}
value={formData.suggestionText}
onChangeText={(value) => handleFieldChange('suggestionText', value)}
placeholder="Describe your suggestion with clinical reasoning..."
@ -565,13 +644,21 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
textAlignVertical="top"
/>
</View>
</View>
{/* Confidence & Priority Card */}
<View style={[styles.formCard, {zIndex:2}]}>
<View style={styles.cardHeader}>
<Icon name="bar-chart-2" size={20} color={theme.colors.primary} />
<Text style={styles.cardTitle}>Assessment & Priority</Text>
</View>
{/* Row 1: Confidence and Priority */}
<View style={styles.formRow}>
<View style={[styles.formGroup, styles.formGroupHalf]}>
<Text style={styles.label}>Confidence (0-1)</Text>
<TextInput
style={styles.input}
style={styles.enhancedInput}
value={formData.confidence}
onChangeText={(value) => handleFieldChange('confidence', value)}
placeholder="0.9979"
@ -584,10 +671,16 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
<Text style={styles.label}>Priority</Text>
<View style={styles.dropdownContainer}>
<TouchableOpacity
style={styles.dropdown}
style={styles.enhancedDropdown}
onPress={() => setShowPriorityDropdown(!showPriorityDropdown)}
>
<View style={styles.dropdownContent}>
<View style={[
styles.priorityIndicator,
{ backgroundColor: PRIORITY_OPTIONS.find(opt => opt.value === formData.priority)?.bgColor || theme.colors.backgroundAlt }
]} />
<Text style={styles.dropdownText}>{formData.priority}</Text>
</View>
<Icon name="chevron-down" size={20} color={theme.colors.textSecondary} />
</TouchableOpacity>
{showPriorityDropdown && renderDropdownOptions(
@ -598,13 +691,21 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
</View>
</View>
</View>
</View>
{/* Category & Cost Card */}
<View style={styles.formCard}>
<View style={styles.cardHeader}>
<Icon name="tag" size={20} color={theme.colors.primary} />
<Text style={styles.cardTitle}>Classification & Resources</Text>
</View>
{/* Row 2: Category and Cost Estimate */}
<View style={styles.formRow}>
<View style={[styles.formGroup, styles.formGroupHalf]}>
<Text style={styles.label}>Category</Text>
<TextInput
style={styles.input}
style={styles.enhancedInput}
value={formData.category}
onChangeText={(value) => handleFieldChange('category', value)}
placeholder="Radiology"
@ -615,7 +716,7 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
<View style={[styles.formGroup, styles.formGroupHalf]}>
<Text style={styles.label}>Cost Estimate (USD)</Text>
<TextInput
style={styles.input}
style={styles.enhancedInput}
value={formData.costEstimate}
onChangeText={(value) => handleFieldChange('costEstimate', value)}
placeholder="Enter cost"
@ -624,13 +725,21 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
/>
</View>
</View>
</View>
{/* Time & Expiry Card */}
<View style={styles.formCard}>
<View style={styles.cardHeader}>
<Icon name="clock" size={20} color={theme.colors.primary} />
<Text style={styles.cardTitle}>Timeline</Text>
</View>
{/* Row 3: Time Estimate and Expires At */}
<View style={styles.formRow}>
<View style={[styles.formGroup, styles.formGroupHalf]}>
<Text style={styles.label}>Time Estimate</Text>
<TextInput
style={styles.input}
style={styles.enhancedInput}
value={formData.timeEstimate}
onChangeText={(value) => handleFieldChange('timeEstimate', value)}
placeholder="1-2 hours"
@ -641,9 +750,10 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
<View style={[styles.formGroup, styles.formGroupHalf]}>
<Text style={styles.label}>Expires At</Text>
<TouchableOpacity
style={styles.dateInput}
style={styles.enhancedDateInput}
onPress={() => setShowDatePicker(true)}
>
<Icon name="calendar" size={18} color={theme.colors.primary} />
<Text style={[
styles.dateText,
!formData.expiresAt && styles.placeholderText
@ -653,17 +763,24 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
: 'Select date'
}
</Text>
<Icon name="calendar" size={20} color={theme.colors.textSecondary} />
</TouchableOpacity>
<Text style={styles.helperText}>Date must be in the future</Text>
</View>
</View>
</View>
{/* AI Model & Evidence Card */}
<View style={styles.formCard}>
<View style={styles.cardHeader}>
<Icon name="cpu" size={20} color={theme.colors.primary} />
<Text style={styles.cardTitle}>AI Model & Evidence</Text>
</View>
{/* AI Model Version */}
<View style={styles.formGroup}>
<Text style={styles.label}>AI Model Version</Text>
<TextInput
style={styles.input}
style={styles.enhancedInput}
value={formData.aiModelVersion}
onChangeText={(value) => handleFieldChange('aiModelVersion', value)}
placeholder="v2.1.0"
@ -675,19 +792,27 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
<View style={styles.formGroup}>
<Text style={styles.label}>Evidence Sources (comma-separated)</Text>
<TextInput
style={styles.input}
style={styles.enhancedInput}
value={formData.evidenceSources}
onChangeText={(value) => handleFieldChange('evidenceSources', value)}
placeholder="Evidence A, Protocol B"
placeholderTextColor={theme.colors.textMuted}
/>
</View>
</View>
{/* Contraindications & Tags Card */}
<View style={styles.formCard}>
<View style={styles.cardHeader}>
<Icon name="alert-triangle" size={20} color={theme.colors.primary} />
<Text style={styles.cardTitle}>Safety & Organization</Text>
</View>
{/* Contraindications */}
<View style={styles.formGroup}>
<Text style={styles.label}>Contraindications</Text>
<TextInput
style={[styles.input, styles.textArea]}
style={[styles.enhancedInput, styles.textArea]}
value={formData.contraindications}
onChangeText={(value) => handleFieldChange('contraindications', value)}
placeholder="Any known contraindications..."
@ -702,13 +827,14 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
<View style={styles.formGroup}>
<Text style={styles.label}>Tags (comma-separated)</Text>
<TextInput
style={styles.input}
style={styles.enhancedInput}
value={formData.tags}
onChangeText={(value) => handleFieldChange('tags', value)}
placeholder="emergency, chest, pulmonary"
placeholderTextColor={theme.colors.textMuted}
/>
</View>
</View>
{/* Related Findings */}
{renderRelatedFindings()}
@ -720,13 +846,23 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
disabled={isSubmitting}
>
{isSubmitting ? (
<View style={styles.submitButtonContent}>
<Icon name="loader" size={20} color={theme.colors.background} style={styles.spinningIcon} />
<Text style={styles.submitButtonText}>Submitting...</Text>
</View>
) : (
<View style={styles.submitButtonContent}>
<Icon name="send" size={20} color={theme.colors.background} />
<Text style={styles.submitButtonText}>Submit Suggestion</Text>
</View>
)}
</TouchableOpacity>
</ScrollView>
</KeyboardAvoidingView>
</View>
</>
</TouchableWithoutFeedback>
{/* Date Picker */}
{showDatePicker && (
@ -771,32 +907,48 @@ const AIPredictionDetailScreen: React.FC<AIPredictionDetailsScreenProps> = ({
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: theme.colors.background,
backgroundColor: '#F8FAFC',
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.md,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
paddingVertical: theme.spacing.lg,
backgroundColor: theme.colors.background,
borderBottomWidth: 1,
borderBottomColor: '#E2E8F0',
shadowColor: '#000000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.06,
shadowRadius: 2,
elevation: 2,
},
backButton: {
padding: theme.spacing.sm,
backgroundColor: '#F1F5F9',
borderRadius: theme.borderRadius.medium,
},
headerContent: {
flex: 1,
alignItems: 'center',
},
headerTitle: {
fontSize: theme.typography.fontSize.displaySmall,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
marginBottom: 2,
},
headerSubtitle: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textSecondary,
},
headerSpacer: {
width: 40,
},
keyboardAvoidingView: {
flex: 1,
// Ensure this doesn't interfere with bottom tab navigation
position: 'relative',
},
scrollView: {
@ -806,6 +958,62 @@ const styles = StyleSheet.create({
padding: theme.spacing.md,
paddingBottom: theme.spacing.xl,
},
mainContent: {
flex: 1,
},
// Card Styles
patientCard: {
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.large,
padding: theme.spacing.lg,
marginBottom: theme.spacing.lg,
shadowColor: '#000000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.05,
shadowRadius: 4,
elevation: 2,
borderLeftWidth: 4,
borderLeftColor: theme.colors.primary,
},
formCard: {
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.large,
padding: theme.spacing.lg,
marginBottom: theme.spacing.lg,
shadowColor: '#000000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.05,
shadowRadius: 4,
elevation: 2,
},
cardHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: theme.spacing.md,
paddingBottom: theme.spacing.sm,
borderBottomWidth: 1,
borderBottomColor: '#E2E8F0',
},
cardTitle: {
fontSize: theme.typography.fontSize.bodyLarge,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
marginLeft: theme.spacing.sm,
},
patientCardHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: theme.spacing.md,
},
patientCardTitle: {
fontSize: theme.typography.fontSize.bodyLarge,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
marginLeft: theme.spacing.sm,
},
// Form Styles
formGroup: {
marginBottom: theme.spacing.md,
},
@ -833,15 +1041,33 @@ const styles = StyleSheet.create({
color: theme.colors.textPrimary,
backgroundColor: theme.colors.background,
},
enhancedInput: {
borderWidth: 1,
borderColor: '#E2E8F0',
borderRadius: theme.borderRadius.medium,
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.md,
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textPrimary,
backgroundColor: '#FFFFFF',
shadowColor: '#000000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.03,
shadowRadius: 1,
elevation: 1,
},
disabledInput: {
backgroundColor: theme.colors.backgroundAlt,
backgroundColor: '#F8FAFC',
color: theme.colors.textSecondary,
borderColor: theme.colors.border,
borderColor: '#E2E8F0',
},
textArea: {
height: 80,
height: 100,
textAlignVertical: 'top',
},
// Dropdown Styles
dropdownContainer: {
position: 'relative',
},
@ -856,35 +1082,69 @@ const styles = StyleSheet.create({
paddingVertical: theme.spacing.sm,
backgroundColor: theme.colors.background,
},
enhancedDropdown: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderWidth: 1,
borderColor: '#E2E8F0',
borderRadius: theme.borderRadius.medium,
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.md,
backgroundColor: '#FFFFFF',
shadowColor: '#000000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.03,
shadowRadius: 1,
elevation: 1,
},
dropdownContent: {
flexDirection: 'row',
alignItems: 'center',
flex: 1,
},
dropdownText: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textPrimary,
marginLeft: theme.spacing.sm,
},
dropdownOptions: {
position: 'absolute',
top: '100%',
left: 0,
right: 0,
zIndex: 1000,
borderWidth: 1,
borderColor: theme.colors.border,
borderColor: '#E2E8F0',
borderRadius: theme.borderRadius.medium,
backgroundColor: theme.colors.background,
backgroundColor: '#FFFFFF',
marginTop: theme.spacing.xs,
...theme.shadows.medium,
shadowColor: '#000000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
dropdownOption: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
paddingVertical: theme.spacing.md,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
borderBottomColor: '#F1F5F9',
},
dropdownOptionContent: {
flexDirection: 'row',
alignItems: 'center',
flex: 1,
},
dropdownOptionIcon: {
marginRight: theme.spacing.sm,
},
dropdownOptionSelected: {
backgroundColor: theme.colors.tertiary,
backgroundColor: '#F8FAFC',
},
dropdownOptionText: {
fontSize: theme.typography.fontSize.bodyMedium,
@ -895,6 +1155,14 @@ const styles = StyleSheet.create({
color: theme.colors.primary,
fontFamily: theme.typography.fontFamily.medium,
},
priorityIndicator: {
width: 12,
height: 12,
borderRadius: 6,
marginRight: theme.spacing.sm,
},
// Date Input Styles
dateInput: {
flexDirection: 'row',
alignItems: 'center',
@ -906,10 +1174,28 @@ const styles = StyleSheet.create({
paddingVertical: theme.spacing.sm,
backgroundColor: theme.colors.background,
},
enhancedDateInput: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderWidth: 1,
borderColor: '#E2E8F0',
borderRadius: theme.borderRadius.medium,
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.md,
backgroundColor: '#FFFFFF',
shadowColor: '#000000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.03,
shadowRadius: 1,
elevation: 1,
},
dateText: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textPrimary,
flex: 1,
marginLeft: theme.spacing.sm,
},
placeholderText: {
color: theme.colors.textMuted,
@ -921,14 +1207,34 @@ const styles = StyleSheet.create({
marginTop: theme.spacing.xs,
fontStyle: 'italic',
},
// Section Styles
sectionHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: theme.spacing.md,
},
sectionTitle: {
fontSize: theme.typography.fontSize.bodyLarge,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.textPrimary,
marginBottom: theme.spacing.md,
marginLeft: theme.spacing.sm,
},
// Related Findings Styles
relatedFindingsContainer: {
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.large,
padding: theme.spacing.lg,
marginBottom: theme.spacing.lg,
shadowColor: '#000000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.05,
shadowRadius: 4,
elevation: 2,
},
relatedFindingsContent: {
marginTop: theme.spacing.sm,
},
relatedFindingItem: {
flexDirection: 'row',
@ -936,11 +1242,20 @@ const styles = StyleSheet.create({
gap: theme.spacing.sm,
marginBottom: theme.spacing.sm,
},
relatedFindingInputs: {
flex: 1,
flexDirection: 'row',
gap: theme.spacing.sm,
},
relatedFindingKey: {
flex: 1,
backgroundColor: '#F8FAFC',
borderColor: '#E2E8F0',
},
relatedFindingValue: {
flex: 1,
backgroundColor: '#F8FAFC',
borderColor: '#E2E8F0',
},
addButton: {
backgroundColor: theme.colors.primary,
@ -949,6 +1264,13 @@ const styles = StyleSheet.create({
paddingVertical: theme.spacing.sm,
alignItems: 'center',
alignSelf: 'flex-start',
flexDirection: 'row',
gap: theme.spacing.sm,
shadowColor: theme.colors.primary,
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 2,
elevation: 2,
},
addButtonText: {
fontSize: theme.typography.fontSize.bodyMedium,
@ -956,26 +1278,43 @@ const styles = StyleSheet.create({
color: theme.colors.background,
},
removeButton: {
padding: theme.spacing.xs,
padding: theme.spacing.sm,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#FEF2F2',
borderRadius: theme.borderRadius.medium,
},
// Submit Button Styles
submitButton: {
backgroundColor: theme.colors.primary,
borderRadius: theme.borderRadius.medium,
paddingVertical: theme.spacing.md,
borderRadius: theme.borderRadius.large,
paddingVertical: theme.spacing.lg,
alignItems: 'center',
marginTop: theme.spacing.lg,
...theme.shadows.medium,
marginTop: theme.spacing.xl,
shadowColor: theme.colors.primary,
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.2,
shadowRadius: 4,
elevation: 4,
},
submitButtonDisabled: {
backgroundColor: theme.colors.textMuted,
shadowOpacity: 0.1,
},
submitButtonContent: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.sm,
},
submitButtonText: {
fontSize: theme.typography.fontSize.bodyLarge,
fontFamily: theme.typography.fontFamily.bold,
color: theme.colors.background,
},
spinningIcon: {
// Add rotation animation if needed
},
});
export default AIPredictionDetailScreen;

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} />

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

@ -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>
</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"
}
]
}