NeoScan_Physician/app/modules/AIPrediction/screens/AIPredictionDetailScreen.tsx
2025-08-20 20:42:33 +05:30

1327 lines
43 KiB
TypeScript

/*
* File: AIPredictionDetailScreen.tsx
* Description: AI Prediction detail screen with suggestion form for updating AI findings
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useState, useCallback, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
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';
import { theme } from '../../../theme';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
import { CustomModal } from '../../../shared/components/CustomModal';
import Toast from 'react-native-toast-message';
import { aiPredictionAPI } from '../services/aiPredictionAPI';
import { showError } from '../../../shared/utils/toast';
// Import Redux selectors
import { selectCurrentCase, selectIsLoadingCaseDetails, selectError } from '../redux';
import { selectUser } from '../../Auth/redux/authSelectors';
// Import types
import type { AIPredictionDetailsScreenProps } from '../navigation/navigationTypes';
import type { AIPredictionCase } from '../types';
// Get screen dimensions
const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
// ============================================================================
// ENUMS & TYPES
// ============================================================================
/**
* Suggestion Type Enum
*
* Purpose: Define available suggestion types as specified by user
*/
export enum SuggestionType {
TREATMENT = 'Treatment',
FOLLOW_UP = 'Follow Up',
DIAGNOSIS = 'Diagnosis',
OTHER = 'Other'
}
/**
* Priority Enum
*
* Purpose: Define priority levels as specified by user
*/
export enum Priority {
LOW = 'Low',
MEDIUM = 'Medium',
HIGH = 'High',
CRITICAL = 'Critical'
}
/**
* Related Finding Interface
*
* Purpose: Structure for key-value pairs in related findings
*/
interface RelatedFinding {
key: string;
value: string;
}
/**
* Suggestion Form Data Interface
*
* Purpose: Structure for suggestion form data
*/
interface SuggestionFormData {
patientId: string;
suggestionType: SuggestionType;
title: string;
suggestionText: string;
confidence: string;
priority: Priority;
category: string;
costEstimate: string;
timeEstimate: string;
expiresAt: Date | null;
aiModelVersion: string;
evidenceSources: string;
contraindications: string;
tags: string;
relatedFindings: RelatedFinding[];
}
/**
* API Response Interface
*
* Purpose: Structure for API response data
*/
interface APIResponse {
success: boolean;
message: string;
data: {
created_at: string;
updated_at: string;
status: string;
suggestion_id: string;
};
}
// ============================================================================
// CONSTANTS
// ============================================================================
const SUGGESTION_TYPE_OPTIONS = [
{ 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: '#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' },
];
// ============================================================================
// AI PREDICTION DETAIL SCREEN COMPONENT
// ============================================================================
/**
* AIPredictionDetailScreen Component
*
* Purpose: Display AI prediction details and allow suggestion updates
*
* Features:
* - View complete AI prediction details
* - Create/update suggestions for AI findings
* - Form validation and submission
* - Dynamic related findings management
* - 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,
route
}) => {
// ============================================================================
// REDUX STATE
// ============================================================================
const dispatch = useAppDispatch();
const user = useAppSelector(selectUser);
const currentCase = useAppSelector(selectCurrentCase);
const isLoading = useAppSelector(selectIsLoadingCaseDetails);
const error = useAppSelector(selectError);
// ============================================================================
// LOCAL STATE
// ============================================================================
const [formData, setFormData] = useState<SuggestionFormData>({
patientId: route.params.caseId,
suggestionType: SuggestionType.TREATMENT,
title: '',
suggestionText: '',
confidence: '0.99',
priority: Priority.HIGH,
category: 'Radiology',
costEstimate: '',
timeEstimate: '1-2 hours',
expiresAt: null,
aiModelVersion: 'v2.1.0',
evidenceSources: '',
contraindications: '',
tags: '',
relatedFindings: [
{ key: 'finding_label', value: 'midline shift' },
{ key: 'finding_type', value: 'no_pathology' },
{ key: 'clinical_urgency', value: 'urgent' },
{ key: 'primary_severity', value: 'high' },
{ key: 'modality', value: '/DX' },
{ key: 'institution', value: 'Brighton Radiology' },
],
});
const [showSuggestionTypeDropdown, setShowSuggestionTypeDropdown] = useState(false);
const [showPriorityDropdown, setShowPriorityDropdown] = useState(false);
const [showDatePicker, setShowDatePicker] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [showSuccessModal, setShowSuccessModal] = useState(false);
const [apiResponse, setApiResponse] = useState<any>(null);
// ============================================================================
// EFFECTS
// ============================================================================
/**
* Initialize form data from current case
*/
useEffect(() => {
if (currentCase) {
setFormData(prev => ({
...prev,
patientId: currentCase.patid,
title: `Recommended CT Scan`,
suggestionText: `Describe your suggestion with clinical reasoning...`,
confidence: currentCase.prediction.confidence_score.toFixed(4),
}));
}
}, [currentCase]);
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Handle form field changes
*/
const handleFieldChange = useCallback((field: keyof SuggestionFormData, value: any) => {
setFormData(prev => ({
...prev,
[field]: value,
}));
}, []);
/**
* Handle dropdown selection
*/
const handleDropdownSelect = useCallback((field: keyof SuggestionFormData, value: any) => {
setFormData(prev => ({
...prev,
[field]: value,
}));
setShowSuggestionTypeDropdown(false);
setShowPriorityDropdown(false);
}, []);
/**
* Close all dropdowns
*/
const closeAllDropdowns = useCallback(() => {
setShowSuggestionTypeDropdown(false);
setShowPriorityDropdown(false);
}, []);
/**
* Handle date selection
*/
const handleDateChange = useCallback((event: any, selectedDate?: Date) => {
setShowDatePicker(false);
if (selectedDate) {
handleFieldChange('expiresAt', selectedDate);
}
}, [handleFieldChange]);
/**
* Add new related finding
*/
const handleAddRelatedFinding = useCallback(() => {
setFormData(prev => ({
...prev,
relatedFindings: [...prev.relatedFindings, { key: '', value: '' }],
}));
}, []);
/**
* Remove related finding
*/
const handleRemoveRelatedFinding = useCallback((index: number) => {
setFormData(prev => ({
...prev,
relatedFindings: prev.relatedFindings.filter((_, i) => i !== index),
}));
}, []);
/**
* Update related finding
*/
const handleUpdateRelatedFinding = useCallback((index: number, field: 'key' | 'value', value: string) => {
setFormData(prev => ({
...prev,
relatedFindings: prev.relatedFindings.map((finding, i) =>
i === index ? { ...finding, [field]: value } : finding
),
}));
}, []);
/**
* Validate form data
*/
const validateForm = useCallback((): boolean => {
if (!formData.title.trim()) {
showError('Validation Error', 'Title is required');
return false;
}
if (!formData.suggestionText.trim()) {
showError('Validation Error', 'Suggestion text is required');
return false;
}
if (!formData.patientId.trim()) {
showError('Validation Error', 'Patient ID is required');
return false;
}
// Validate expiry date - must be in the future
if (formData.expiresAt) {
const now = new Date();
const expiryDate = new Date(formData.expiresAt);
if (expiryDate <= now) {
showError('Validation Error', 'Expiry date must be in the future');
return false;
}
}
return true;
}, [formData]);
/**
* Handle form submission
*/
const handleSubmitSuggestion = useCallback(async () => {
if (!validateForm()) return;
setIsSubmitting(true);
try {
// Prepare API payload according to backend structure
const apiPayload = {
patid: formData.patientId,
suggestion_type: formData.suggestionType.toLowerCase().replace(' ', '_'),
suggestion_title: formData.title,
suggestion_text: formData.suggestionText,
confidence_score: parseFloat(formData.confidence),
priority_level: formData.priority.toLowerCase(),
category: formData.category,
related_findings: formData.relatedFindings.reduce((acc, finding) => {
if (finding.key && finding.value) {
acc[finding.key] = finding.value;
}
return acc;
}, {} as Record<string, string>),
evidence_sources: formData.evidenceSources ? formData.evidenceSources.split(',').map(s => s.trim()) : [],
contraindications: formData.contraindications || 'none',
cost_estimate: formData.costEstimate ? parseFloat(formData.costEstimate) : 0,
time_estimate: formData.timeEstimate,
expires_at: formData.expiresAt ? formData.expiresAt.toISOString() : null,
tags: formData.tags ? formData.tags.split(',').map(s => s.trim()) : [],
ai_model_version: formData.aiModelVersion,
};
// Call centralized API service
const response = await aiPredictionAPI.submitAISuggestion(apiPayload, user?.access_token || '');
console.log('feed back response',response)
if (response.ok && response.data) {
const responseData = response.data as APIResponse;
if (responseData.success) {
// Show success toast
Toast.show({
type: 'success',
text1: 'Success!',
text2: responseData.message || 'Suggestion submitted successfully',
position: 'top',
visibilityTime: 4000,
});
// Store API response data
setApiResponse(responseData.data);
// Show success modal with API response data
setShowSuccessModal(true);
} else {
throw new Error(responseData.message || 'Failed to submit suggestion');
}
} else {
throw new Error(response.problem || 'HTTP request failed');
}
} catch (error) {
console.error('Submission Error:', error);
// Show error toast
Toast.show({
type: 'error',
text1: 'Error',
text2: error instanceof Error ? error.message : 'Failed to submit suggestion. Please try again.',
position: 'top',
visibilityTime: 5000,
});
} finally {
setIsSubmitting(false);
}
}, [formData, validateForm, user]);
/**
* Handle success modal close
*/
const handleSuccessModalClose = useCallback(() => {
setShowSuccessModal(false);
navigation.goBack();
}, [navigation]);
/**
* Handle back navigation
*/
const handleGoBack = useCallback(() => {
navigation.goBack();
}, [navigation]);
// ============================================================================
// RENDER FUNCTIONS
// ============================================================================
/**
* 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; bgColor?: string; icon?: string }>,
currentValue: any,
onSelect: (value: any) => void
) => (
<View style={styles.dropdownOptions}>
{options.map((option) => (
<TouchableOpacity
key={option.value}
style={[
styles.dropdownOption,
currentValue === option.value && styles.dropdownOptionSelected
]}
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,
option.color && { color: option.color }
]}>
{option.label}
</Text>
</View>
{currentValue === option.value && (
<Icon name="check" size={16} color={theme.colors.primary} />
)}
</TouchableOpacity>
))}
</View>
), []);
/**
* 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="Finding Key"
placeholderTextColor={theme.colors.textMuted}
/>
<TextInput
style={[styles.input, styles.relatedFindingValue]}
value={finding.value}
onChangeText={(value) => handleUpdateRelatedFinding(index, 'value', value)}
placeholder="Finding Value"
placeholderTextColor={theme.colors.textMuted}
/>
</View>
<TouchableOpacity
style={styles.removeButton}
onPress={() => handleRemoveRelatedFinding(index)}
accessibilityRole="button"
accessibilityLabel="Remove related finding"
>
<Icon name="trash-2" size={18} color={theme.colors.error} />
</TouchableOpacity>
</View>
))}
<TouchableOpacity
style={styles.addButton}
onPress={handleAddRelatedFinding}
>
<Icon name="plus" size={20} color={theme.colors.background} />
<Text style={styles.addButtonText}>Add Finding</Text>
</TouchableOpacity>
</View>
</View>
), [formData.relatedFindings, handleUpdateRelatedFinding, handleRemoveRelatedFinding, handleAddRelatedFinding]);
// ============================================================================
// RENDER
// ============================================================================
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor={theme.colors.background} />
<TouchableWithoutFeedback onPress={closeAllDropdowns} disabled={!showSuggestionTypeDropdown && !showPriorityDropdown}>
<View style={{flex:1}}>
{/* Enhanced Header */}
{renderHeader()}
<View style={styles.mainContent}>
<KeyboardAvoidingView
style={styles.keyboardAvoidingView}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 0}
enabled={Platform.OS === 'ios'}
>
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
showsVerticalScrollIndicator={false}
keyboardShouldPersistTaps="handled"
>
{/* 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
style={[styles.input, styles.disabledInput]}
value={formData.patientId}
editable={false}
placeholder="Patient ID"
placeholderTextColor={theme.colors.textMuted}
/>
</View>
</View>
{/* 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}>Type</Text>
<View style={styles.dropdownContainer}>
<TouchableOpacity
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(
SUGGESTION_TYPE_OPTIONS,
formData.suggestionType,
(value) => handleDropdownSelect('suggestionType', value)
)}
</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.enhancedInput}
value={formData.title}
onChangeText={(value) => handleFieldChange('title', value)}
placeholder="Recommended CT Scan"
placeholderTextColor={theme.colors.textMuted}
/>
</View>
{/* Suggestion Text */}
<View style={styles.formGroup}>
<Text style={styles.label}>Suggestion Text</Text>
<TextInput
style={[styles.enhancedInput, styles.textArea]}
value={formData.suggestionText}
onChangeText={(value) => handleFieldChange('suggestionText', value)}
placeholder="Describe your suggestion with clinical reasoning..."
placeholderTextColor={theme.colors.textMuted}
multiline
numberOfLines={4}
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.enhancedInput}
value={formData.confidence}
onChangeText={(value) => handleFieldChange('confidence', value)}
placeholder="0.9979"
placeholderTextColor={theme.colors.textMuted}
keyboardType="numeric"
/>
</View>
<View style={[styles.formGroup, styles.formGroupHalf]}>
<Text style={styles.label}>Priority</Text>
<View style={styles.dropdownContainer}>
<TouchableOpacity
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(
PRIORITY_OPTIONS,
formData.priority,
(value) => handleDropdownSelect('priority', value)
)}
</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.enhancedInput}
value={formData.category}
onChangeText={(value) => handleFieldChange('category', value)}
placeholder="Radiology"
placeholderTextColor={theme.colors.textMuted}
/>
</View>
<View style={[styles.formGroup, styles.formGroupHalf]}>
<Text style={styles.label}>Cost Estimate (USD)</Text>
<TextInput
style={styles.enhancedInput}
value={formData.costEstimate}
onChangeText={(value) => handleFieldChange('costEstimate', value)}
placeholder="Enter cost"
placeholderTextColor={theme.colors.textMuted}
keyboardType="numeric"
/>
</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.enhancedInput}
value={formData.timeEstimate}
onChangeText={(value) => handleFieldChange('timeEstimate', value)}
placeholder="1-2 hours"
placeholderTextColor={theme.colors.textMuted}
/>
</View>
<View style={[styles.formGroup, styles.formGroupHalf]}>
<Text style={styles.label}>Expires At</Text>
<TouchableOpacity
style={styles.enhancedDateInput}
onPress={() => setShowDatePicker(true)}
>
<Icon name="calendar" size={18} color={theme.colors.primary} />
<Text style={[
styles.dateText,
!formData.expiresAt && styles.placeholderText
]}>
{formData.expiresAt
? formData.expiresAt.toLocaleDateString()
: 'Select date'
}
</Text>
</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.enhancedInput}
value={formData.aiModelVersion}
onChangeText={(value) => handleFieldChange('aiModelVersion', value)}
placeholder="v2.1.0"
placeholderTextColor={theme.colors.textMuted}
/>
</View>
{/* Evidence Sources */}
<View style={styles.formGroup}>
<Text style={styles.label}>Evidence Sources (comma-separated)</Text>
<TextInput
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.enhancedInput, styles.textArea]}
value={formData.contraindications}
onChangeText={(value) => handleFieldChange('contraindications', value)}
placeholder="Any known contraindications..."
placeholderTextColor={theme.colors.textMuted}
multiline
numberOfLines={3}
textAlignVertical="top"
/>
</View>
{/* Tags */}
<View style={styles.formGroup}>
<Text style={styles.label}>Tags (comma-separated)</Text>
<TextInput
style={styles.enhancedInput}
value={formData.tags}
onChangeText={(value) => handleFieldChange('tags', value)}
placeholder="emergency, chest, pulmonary"
placeholderTextColor={theme.colors.textMuted}
/>
</View>
</View>
{/* Related Findings */}
{renderRelatedFindings()}
{/* Submit Button */}
<TouchableOpacity
style={[styles.submitButton, isSubmitting && styles.submitButtonDisabled]}
onPress={handleSubmitSuggestion}
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>
</View>
</TouchableWithoutFeedback>
{/* Date Picker */}
{showDatePicker && (
<DateTimePicker
value={formData.expiresAt || new Date()}
mode="date"
display="default"
onChange={handleDateChange}
minimumDate={new Date()}
/>
)}
{/* Success Modal */}
<CustomModal
visible={showSuccessModal}
title="Suggestion Submitted Successfully!"
message={
apiResponse ?
`Your suggestion has been submitted and will be reviewed by the medical team.\n\n` +
`Suggestion ID: ${apiResponse.suggestion_id}\n` +
`Status: ${apiResponse.status}\n` +
`Created: ${new Date(apiResponse.created_at).toLocaleString()}`
:
"Your suggestion has been successfully submitted and will be reviewed by the medical team."
}
type="success"
confirmText="OK"
onConfirm={handleSuccessModalClose}
onClose={handleSuccessModalClose}
/>
{/* Toast Messages */}
<Toast />
</SafeAreaView>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F8FAFC',
},
header: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: theme.spacing.md,
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,
position: 'relative',
},
scrollView: {
flex: 1,
},
scrollContent: {
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,
},
formGroupHalf: {
flex: 1,
},
formRow: {
flexDirection: 'row',
gap: theme.spacing.md,
},
label: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.textPrimary,
marginBottom: theme.spacing.sm,
},
input: {
borderWidth: 1,
borderColor: theme.colors.border,
borderRadius: theme.borderRadius.medium,
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.regular,
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: '#F8FAFC',
color: theme.colors.textSecondary,
borderColor: '#E2E8F0',
},
textArea: {
height: 100,
textAlignVertical: 'top',
},
// Dropdown Styles
dropdownContainer: {
position: 'relative',
},
dropdown: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
borderWidth: 1,
borderColor: theme.colors.border,
borderRadius: theme.borderRadius.medium,
paddingHorizontal: theme.spacing.md,
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,
borderWidth: 1,
borderColor: '#E2E8F0',
borderRadius: theme.borderRadius.medium,
backgroundColor: '#FFFFFF',
marginTop: theme.spacing.xs,
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.md,
borderBottomWidth: 1,
borderBottomColor: '#F1F5F9',
},
dropdownOptionContent: {
flexDirection: 'row',
alignItems: 'center',
flex: 1,
},
dropdownOptionIcon: {
marginRight: theme.spacing.sm,
},
dropdownOptionSelected: {
backgroundColor: '#F8FAFC',
},
dropdownOptionText: {
fontSize: theme.typography.fontSize.bodyMedium,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textPrimary,
},
dropdownOptionTextSelected: {
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',
justifyContent: 'space-between',
borderWidth: 1,
borderColor: theme.colors.border,
borderRadius: theme.borderRadius.medium,
paddingHorizontal: theme.spacing.md,
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,
},
helperText: {
fontSize: theme.typography.fontSize.bodySmall,
fontFamily: theme.typography.fontFamily.regular,
color: theme.colors.textMuted,
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,
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',
alignItems: 'center',
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,
borderRadius: theme.borderRadius.medium,
paddingHorizontal: theme.spacing.md,
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,
fontFamily: theme.typography.fontFamily.medium,
color: theme.colors.background,
},
removeButton: {
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.large,
paddingVertical: theme.spacing.lg,
alignItems: 'center',
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;
/*
* End of File: AIPredictionDetailScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/