NeoScan_Physician/app/modules/PatientCare/screens/FeedbackDetailScreen.tsx
2025-08-25 14:43:02 +05:30

633 lines
18 KiB
TypeScript

/*
* File: FeedbackDetailScreen.tsx
* Description: Feedback detail screen for a specific series showing feedback history (read-only)
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*
* Features:
* - Display feedback history for the series (read-only)
* - Feedback data received from navigation parameters
* - Clinical insights and feedback analytics
* - Modern healthcare-focused UI design
*/
import React, { useEffect, useState, useCallback } from 'react';
import {
View,
Text,
StyleSheet,
ScrollView,
TouchableOpacity,
Alert,
TextInput,
RefreshControl,
} from 'react-native';
import { theme } from '../../../theme/theme';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
import Icon from 'react-native-vector-icons/Feather';
import { SafeAreaView } from 'react-native-safe-area-context';
// Import types and API
import { selectUser } from '../../Auth/redux/authSelectors';
import { FeedbackDetailScreenProps } from '../../Dashboard/navigation/navigationTypes';
// ============================================================================
// INTERFACES
// ============================================================================
interface SeriesSummary {
series_num: string;
series_description: string;
total_images: number;
png_preview: string;
modality: string;
}
interface Feedback {
feedback_id: string;
user_id: string;
feedback_text: string;
is_positive: boolean;
email: string;
created_at: string;
prediction_id: number;
prediction_file_path: string;
series_number: string;
feedback_type: string;
}
interface PatientData {
patid: string;
hospital_id: string;
patient_info: {
name: string;
age: string;
sex: string;
date: string;
institution: string;
modality: string;
status: string;
report_status: string;
file_name: string;
file_type: string;
frame_count: number;
};
series_summary: SeriesSummary[];
processing_metadata: any;
total_predictions: number;
first_processed_at: string;
last_processed_at: string;
}
// ============================================================================
// FEEDBACK DETAIL SCREEN COMPONENT
// ============================================================================
/**
* FeedbackDetailScreen Component
*
* Purpose: Display feedback details and history for a specific series (read-only)
*
* Features:
* - Feedback history display (read-only)
* - Clinical insights and analytics
* - Modern healthcare-focused UI design
*/
const FeedbackDetailScreen: React.FC<FeedbackDetailScreenProps> = ({ navigation, route }) => {
// ============================================================================
// STATE MANAGEMENT
// ============================================================================
const dispatch = useAppDispatch();
// Route parameters
const { patientId, patientName, seriesNumber, seriesData, patientData, feedbackData, onFeedbackSubmitted } = route.params;
// Redux state
const user = useAppSelector(selectUser);
// Local state
const [isLoading, setIsLoading] = useState(false);
const [isRefreshing, setIsRefreshing] = useState(false);
const [error, setError] = useState<string | null>(null);
// ============================================================================
// EFFECTS
// ============================================================================
/**
* Component Mount Effect
*
* Purpose: Initialize screen and set navigation title
*/
useEffect(() => {
navigation.setOptions({
title: `Feedback - Series ${seriesNumber}`,
headerShown: true,
headerLeft: () => (
<TouchableOpacity
style={styles.headerBackButton}
onPress={handleBackPress}
activeOpacity={0.7}
>
<Icon name="arrow-left" size={24} color={theme.colors.primary} />
</TouchableOpacity>
),
});
}, [navigation, seriesNumber, patientId]);
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Handle Back Navigation
*
* Purpose: Navigate back to previous screen
*/
const handleBackPress = useCallback(() => {
navigation.goBack();
}, [navigation]);
/**
* Handle Refresh
*
* Purpose: Pull-to-refresh functionality
*/
const handleRefresh = useCallback(async () => {
setIsRefreshing(true);
// TODO: Implement actual refresh logic
setTimeout(() => {
setIsRefreshing(false);
}, 1000);
}, []);
/**
* Handle Back to Patient Details
*
* Purpose: Navigate to PatientDetails screen within the Dashboard stack
*
* Note: Now that both screens are in the same Dashboard stack,
* navigation should work smoothly without loops.
*/
const handleBackToPatientDetails = useCallback(() => {
try {
// Navigate to PatientDetails screen in the same stack
navigation.navigate('PatientDetails', {
patientId: patientId,
patient: patientData || { name: patientName || 'Unknown Patient' },
});
} catch (error) {
console.warn('Navigation to PatientDetails failed:', error);
// Fallback: go back to previous screen
navigation.goBack();
}
}, [navigation, patientId, patientName, patientData]);
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
/**
* Get Feedback Type Color
*
* Purpose: Get appropriate color for feedback type
*
* @param feedbackType - Type of feedback
*/
const getFeedbackTypeColor = (feedbackType: string) => {
switch (feedbackType.toLowerCase()) {
case 'clinical_accuracy':
return theme.colors.success;
case 'confidence_assessment':
return theme.colors.warning;
case 'technical_issue':
return theme.colors.error;
default:
return theme.colors.info;
}
};
/**
* Get Series Feedback
*
* Purpose: Get feedback for the current series
*/
const getSeriesFeedback = () => {
return feedbackData?.filter((feedback: Feedback) => feedback.series_number === seriesNumber) || [];
};
/**
* Is Feedback New
*
* Purpose: Check if feedback is recent (within 24 hours)
*
* @param feedbackId - Feedback ID to check
*/
const isFeedbackNew = (feedbackId: string) => {
const feedback = feedbackData?.find((f: Feedback) => f.feedback_id === feedbackId);
if (!feedback) return false;
const feedbackDate = new Date(feedback.created_at);
const now = new Date();
const diffHours = (now.getTime() - feedbackDate.getTime()) / (1000 * 60 * 60);
return diffHours < 24;
};
// ============================================================================
// RENDER HELPERS
// ============================================================================
/**
* Render Series Header
*
* Purpose: Render series information header
*/
const renderSeriesHeader = () => {
if (!seriesData) return null;
return (
<View style={styles.seriesHeader}>
<View style={styles.seriesHeaderTop}>
<View style={styles.seriesHeaderLeft}>
<Text style={styles.seriesTitle}>
Series {seriesData.series_description}
</Text>
<Text style={styles.seriesMeta}>
{seriesData.total_images} images {seriesData.modality} modality
</Text>
</View>
<View style={styles.seriesHeaderRight}>
<View style={styles.seriesStatusBadge}>
<Icon name="check-circle" size={16} color={'white'} />
<Text style={styles.seriesStatusText}>Processed</Text>
</View>
</View>
</View>
{patientData && (
<View style={styles.patientInfoRow}>
<Icon name="user" size={16} color={theme.colors.textSecondary} />
<Text style={styles.patientInfoText}>
{patientData.patient_info.name} ID: {patientData.patid}
</Text>
<TouchableOpacity
style={styles.patientDetailButton}
onPress={handleBackToPatientDetails}
activeOpacity={0.7}
>
<View style={styles.patientDetailButtonContent}>
<Icon name="user" size={14} color={theme.colors.primary} />
<Text style={styles.patientDetailButtonText}>View Details</Text>
</View>
</TouchableOpacity>
</View>
)}
</View>
);
};
/**
* Render Feedback History
*
* Purpose: Render feedback history display only
*/
const renderFeedbackHistory = () => {
const seriesFeedbacks = getSeriesFeedback();
return (
<View style={styles.feedbackHistory}>
{/* Feedback History */}
<View style={styles.feedbackList}>
<Text style={styles.feedbackListTitle}>
Feedback History ({seriesFeedbacks.length})
</Text>
{seriesFeedbacks.length === 0 ? (
<View style={styles.emptyFeedbackState}>
<Icon name="message-circle" size={48} color={theme.colors.textMuted} />
<Text style={styles.emptyFeedbackTitle}>No Feedback Yet</Text>
<Text style={styles.emptyFeedbackSubtitle}>
No feedback has been provided for this series yet
</Text>
</View>
) : (
seriesFeedbacks.map((feedback: Feedback) => (
<View key={feedback.feedback_id} style={styles.feedbackCard}>
<View style={styles.feedbackCardHeader}>
<View style={styles.feedbackCardHeaderLeft}>
<View style={[
styles.feedbackTypeIndicator,
{ backgroundColor: feedback.is_positive ? theme.colors.success : theme.colors.error }
]}>
<Icon
name={feedback.is_positive ? "thumbs-up" : "thumbs-down"}
size={12}
color={theme.colors.background}
/>
</View>
<Text style={styles.feedbackEmail}>{feedback.email}</Text>
{isFeedbackNew(feedback.feedback_id) && (
<View style={styles.newFeedbackBadge}>
<Text style={styles.newFeedbackBadgeText}>NEW</Text>
</View>
)}
</View>
<Text style={styles.feedbackTimestamp}>
{new Date(feedback.created_at).toLocaleDateString()}
</Text>
</View>
<Text style={styles.feedbackText}>{feedback.feedback_text}</Text>
<View style={styles.feedbackCardFooter}>
<View style={[
styles.feedbackTypeBadge,
{ backgroundColor: getFeedbackTypeColor(feedback.feedback_type) }
]}>
<Text style={styles.feedbackTypeBadgeText}>
{feedback.feedback_type.replace('_', ' ').toUpperCase()}
</Text>
</View>
</View>
</View>
))
)}
</View>
</View>
);
};
// ============================================================================
// MAIN RENDER
// ============================================================================
return (
<SafeAreaView style={styles.container}>
{/* Fixed Series Header */}
{renderSeriesHeader()}
{/* Scrollable Feedback Content */}
<View style={styles.scrollableContent}>
<ScrollView
style={styles.content}
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={isRefreshing}
onRefresh={handleRefresh}
colors={[theme.colors.primary]}
tintColor={theme.colors.primary}
/>
}
>
{/* Feedback History from Navigation Parameters */}
{renderFeedbackHistory()}
</ScrollView>
</View>
</SafeAreaView>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: theme.colors.background,
},
// Series Header Styles
seriesHeader: {
backgroundColor: theme.colors.background,
padding: theme.spacing.md,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
},
// Fixed Feedback Title Styles
fixedFeedbackTitle: {
backgroundColor: theme.colors.background,
padding: theme.spacing.md,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
},
seriesHeaderTop: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: theme.spacing.sm,
},
seriesHeaderLeft: {
flex: 1,
marginRight: theme.spacing.md,
},
seriesHeaderRight: {
alignItems: 'flex-end',
},
seriesTitle: {
fontSize: 18,
color: theme.colors.textPrimary,
fontFamily: theme.typography.fontFamily.bold,
marginBottom: 4,
},
seriesMeta: {
fontSize: 14,
color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.regular,
},
seriesStatusBadge: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.colors.success,
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 12,
},
seriesStatusText: {
fontSize: 10,
color: theme.colors.background,
fontFamily: theme.typography.fontFamily.bold,
marginLeft: 4,
textTransform: 'uppercase',
},
patientInfoRow: {
flexDirection: 'row',
alignItems: 'center',
},
patientInfoText: {
fontSize: 14,
color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.regular,
marginLeft: theme.spacing.xs,
flex: 1,
},
patientDetailButton: {
padding: theme.spacing.xs,
marginLeft: theme.spacing.sm,
backgroundColor: theme.colors.tertiary,
borderRadius: 16,
borderWidth: 1,
borderColor: theme.colors.primary,
},
patientDetailButtonContent: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
paddingVertical: 4,
},
patientDetailButtonText: {
fontSize: 11,
color: theme.colors.primary,
fontFamily: theme.typography.fontFamily.medium,
marginLeft: 4,
textTransform: 'uppercase',
},
// Content Styles
scrollableContent: {
flex: 1,
},
content: {
flex: 1,
},
// Section Styles
sectionTitle: {
fontSize: 18,
color: theme.colors.textPrimary,
fontFamily: theme.typography.fontFamily.bold,
marginBottom: theme.spacing.md,
},
// Feedback Styles
feedbackHistory: {
padding: theme.spacing.md,
},
// Feedback List Styles
feedbackList: {
marginTop: theme.spacing.sm,
},
feedbackListTitle: {
fontSize: 16,
color: theme.colors.textPrimary,
fontFamily: theme.typography.fontFamily.bold,
marginBottom: theme.spacing.md,
},
feedbackCard: {
backgroundColor: theme.colors.backgroundAlt,
borderRadius: 8,
padding: theme.spacing.md,
marginBottom: theme.spacing.sm,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 2,
},
feedbackCardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: theme.spacing.sm,
},
feedbackCardHeaderLeft: {
flexDirection: 'row',
alignItems: 'center',
flex: 1,
},
feedbackTypeIndicator: {
width: 20,
height: 20,
borderRadius: 10,
justifyContent: 'center',
alignItems: 'center',
marginRight: theme.spacing.sm,
},
feedbackEmail: {
fontSize: 12,
color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.medium,
marginRight: theme.spacing.sm,
},
newFeedbackBadge: {
backgroundColor: theme.colors.error,
paddingHorizontal: 6,
paddingVertical: 2,
borderRadius: 8,
},
newFeedbackBadgeText: {
fontSize: 8,
color: theme.colors.background,
fontFamily: theme.typography.fontFamily.bold,
textTransform: 'uppercase',
},
feedbackTimestamp: {
fontSize: 10,
color: theme.colors.textMuted,
fontFamily: theme.typography.fontFamily.regular,
},
feedbackText: {
fontSize: 14,
color: theme.colors.textPrimary,
fontFamily: theme.typography.fontFamily.regular,
lineHeight: 20,
marginBottom: theme.spacing.sm,
},
feedbackCardFooter: {
alignItems: 'flex-end',
},
feedbackTypeBadge: {
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 12,
},
feedbackTypeBadgeText: {
fontSize: 10,
color: theme.colors.background,
fontFamily: theme.typography.fontFamily.bold,
textTransform: 'uppercase',
},
// Empty State Styles
emptyFeedbackState: {
alignItems: 'center',
justifyContent: 'center',
paddingVertical: theme.spacing.xl,
},
emptyFeedbackTitle: {
fontSize: 18,
color: theme.colors.textPrimary,
fontFamily: theme.typography.fontFamily.bold,
marginTop: theme.spacing.md,
marginBottom: theme.spacing.sm,
},
emptyFeedbackSubtitle: {
fontSize: 14,
color: theme.colors.textSecondary,
fontFamily: theme.typography.fontFamily.regular,
textAlign: 'center',
lineHeight: 20,
},
// Header Back Button Style
headerBackButton: {
padding: theme.spacing.sm,
marginLeft: theme.spacing.xs,
},
});
export default FeedbackDetailScreen;
/*
* End of File: FeedbackDetailScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/