ai prediction data mapped to the new tab called ai prediction

This commit is contained in:
yashwin-foxy 2025-08-08 19:03:14 +05:30
parent 84b63e401f
commit e8492cd442
30 changed files with 6322 additions and 22 deletions

View File

@ -0,0 +1,249 @@
/*
* File: AIPredictionCard.test.tsx
* Description: Unit tests for AI Prediction Card component
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import AIPredictionCard from '../components/AIPredictionCard';
import type { AIPredictionCase } from '../types';
// ============================================================================
// MOCK DATA
// ============================================================================
const mockPredictionCase: AIPredictionCase = {
patid: 'test-patient-001',
hospital_id: 'hospital-123',
prediction: {
label: 'midline shift',
finding_type: 'pathology',
clinical_urgency: 'urgent',
confidence_score: 0.96,
finding_category: 'abnormal',
primary_severity: 'high',
anatomical_location: 'brain',
},
created_at: '2024-01-15T10:30:00Z',
updated_at: '2024-01-15T10:30:00Z',
review_status: 'pending',
priority: 'critical',
};
const mockProps = {
predictionCase: mockPredictionCase,
onPress: jest.fn(),
onReview: jest.fn(),
onToggleSelect: jest.fn(),
isSelected: false,
showReviewButton: true,
};
// ============================================================================
// UNIT TESTS
// ============================================================================
describe('AIPredictionCard', () => {
beforeEach(() => {
jest.clearAllMocks();
});
// ============================================================================
// RENDERING TESTS
// ============================================================================
describe('Rendering', () => {
it('should render correctly with required props', () => {
const { getByText } = render(
<AIPredictionCard
predictionCase={mockPredictionCase}
onPress={mockProps.onPress}
/>
);
expect(getByText('test-patient-001')).toBeTruthy();
expect(getByText('Midline Shift')).toBeTruthy();
expect(getByText('96%')).toBeTruthy();
expect(getByText('Urgent')).toBeTruthy();
});
it('should render review button when showReviewButton is true', () => {
const { getByText } = render(<AIPredictionCard {...mockProps} />);
expect(getByText('Review')).toBeTruthy();
});
it('should not render review button when showReviewButton is false', () => {
const { queryByText } = render(
<AIPredictionCard {...mockProps} showReviewButton={false} />
);
expect(queryByText('Review')).toBeNull();
});
it('should not render review button when status is not pending', () => {
const reviewedCase = {
...mockPredictionCase,
review_status: 'reviewed' as const,
};
const { queryByText } = render(
<AIPredictionCard
{...mockProps}
predictionCase={reviewedCase}
/>
);
expect(queryByText('Review')).toBeNull();
});
it('should render selection checkbox when onToggleSelect is provided', () => {
const { getByRole } = render(<AIPredictionCard {...mockProps} />);
expect(getByRole('checkbox')).toBeTruthy();
});
it('should show selected state correctly', () => {
const { getByRole } = render(
<AIPredictionCard {...mockProps} isSelected={true} />
);
const checkbox = getByRole('checkbox');
expect(checkbox.props.accessibilityState.checked).toBe(true);
});
});
// ============================================================================
// INTERACTION TESTS
// ============================================================================
describe('Interactions', () => {
it('should call onPress when card is pressed', () => {
const { getByRole } = render(<AIPredictionCard {...mockProps} />);
fireEvent.press(getByRole('button'));
expect(mockProps.onPress).toHaveBeenCalledWith(mockPredictionCase);
});
it('should call onReview when review button is pressed', () => {
const { getByText } = render(<AIPredictionCard {...mockProps} />);
fireEvent.press(getByText('Review'));
expect(mockProps.onReview).toHaveBeenCalledWith('test-patient-001');
});
it('should call onToggleSelect when checkbox is pressed', () => {
const { getByRole } = render(<AIPredictionCard {...mockProps} />);
fireEvent.press(getByRole('checkbox'));
expect(mockProps.onToggleSelect).toHaveBeenCalledWith('test-patient-001');
});
});
// ============================================================================
// DATA FORMATTING TESTS
// ============================================================================
describe('Data formatting', () => {
it('should format confidence score as percentage', () => {
const { getByText } = render(<AIPredictionCard {...mockProps} />);
expect(getByText('96%')).toBeTruthy();
});
it('should capitalize text correctly', () => {
const { getByText } = render(<AIPredictionCard {...mockProps} />);
expect(getByText('Midline Shift')).toBeTruthy();
expect(getByText('Pathology')).toBeTruthy();
expect(getByText('Abnormal')).toBeTruthy();
});
it('should handle missing anatomical location', () => {
const caseWithoutLocation = {
...mockPredictionCase,
prediction: {
...mockPredictionCase.prediction,
anatomical_location: 'not_applicable',
},
};
const { queryByText } = render(
<AIPredictionCard
{...mockProps}
predictionCase={caseWithoutLocation}
/>
);
// Should not render location when it's 'not_applicable'
expect(queryByText('Not Applicable')).toBeNull();
});
});
// ============================================================================
// ACCESSIBILITY TESTS
// ============================================================================
describe('Accessibility', () => {
it('should have proper accessibility labels', () => {
const { getByLabelText } = render(<AIPredictionCard {...mockProps} />);
expect(
getByLabelText('AI Prediction case for patient test-patient-001')
).toBeTruthy();
});
it('should have proper accessibility hints', () => {
const { getByRole } = render(<AIPredictionCard {...mockProps} />);
const cardButton = getByRole('button');
expect(cardButton.props.accessibilityHint).toBe(
'Tap to view detailed prediction information'
);
});
});
// ============================================================================
// EDGE CASES TESTS
// ============================================================================
describe('Edge cases', () => {
it('should handle missing dates gracefully', () => {
const caseWithoutDates = {
...mockPredictionCase,
created_at: undefined,
updated_at: undefined,
};
const { getByText } = render(
<AIPredictionCard
{...mockProps}
predictionCase={caseWithoutDates}
/>
);
expect(getByText('N/A')).toBeTruthy();
});
it('should handle emergency urgency with special styling', () => {
const emergencyCase = {
...mockPredictionCase,
prediction: {
...mockPredictionCase.prediction,
clinical_urgency: 'emergency' as const,
},
};
const { getByText } = render(
<AIPredictionCard
{...mockProps}
predictionCase={emergencyCase}
/>
);
expect(getByText('Emergency')).toBeTruthy();
});
});
});
/*
* End of File: AIPredictionCard.test.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,361 @@
/*
* File: aiPredictionAPI.test.ts
* Description: Unit tests for AI Prediction API service
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { aiPredictionAPI } from '../services/aiPredictionAPI';
// Mock apisauce
jest.mock('apisauce', () => ({
create: jest.fn(() => ({
get: jest.fn(),
post: jest.fn(),
put: jest.fn(),
delete: jest.fn(),
})),
}));
// Mock API utilities
jest.mock('../../../shared/utils', () => ({
API_CONFIG: {
BASE_URL: 'https://test-api.com',
},
buildHeaders: jest.fn((options = {}) => ({
headers: {
'Content-Type': 'application/json',
...(options.token && { Authorization: `Bearer ${options.token}` }),
},
})),
}));
// ============================================================================
// MOCK DATA
// ============================================================================
const mockToken = 'test-token-123';
const mockResponse = {
ok: true,
data: {
success: true,
data: [
{
patid: 'test-001',
hospital_id: 'hospital-001',
prediction: {
label: 'test finding',
finding_type: 'pathology',
clinical_urgency: 'urgent',
confidence_score: 0.95,
finding_category: 'abnormal',
primary_severity: 'high',
anatomical_location: 'brain',
},
},
],
},
};
// ============================================================================
// UNIT TESTS
// ============================================================================
describe('AI Prediction API', () => {
let mockApi: any;
beforeEach(() => {
// Reset mocks
jest.clearAllMocks();
// Get the mocked API instance
const { create } = require('apisauce');
mockApi = create();
});
// ============================================================================
// GET ALL PREDICTIONS TESTS
// ============================================================================
describe('getAllPredictions', () => {
it('should call GET endpoint with correct parameters', async () => {
mockApi.get.mockResolvedValue(mockResponse);
const params = {
page: 1,
limit: 20,
urgency: 'urgent',
search: 'test',
};
await aiPredictionAPI.getAllPredictions(mockToken, params);
expect(mockApi.get).toHaveBeenCalledWith(
'/api/ai-cases/all-prediction-results',
params,
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
it('should call GET endpoint without parameters', async () => {
mockApi.get.mockResolvedValue(mockResponse);
await aiPredictionAPI.getAllPredictions(mockToken);
expect(mockApi.get).toHaveBeenCalledWith(
'/api/ai-cases/all-prediction-results',
{},
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
});
// ============================================================================
// GET CASE DETAILS TESTS
// ============================================================================
describe('getCaseDetails', () => {
it('should call GET endpoint with correct case ID', async () => {
const caseId = 'test-case-001';
mockApi.get.mockResolvedValue(mockResponse);
await aiPredictionAPI.getCaseDetails(caseId, mockToken);
expect(mockApi.get).toHaveBeenCalledWith(
`/api/ai-cases/prediction-details/${caseId}`,
{},
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
});
// ============================================================================
// UPDATE CASE REVIEW TESTS
// ============================================================================
describe('updateCaseReview', () => {
it('should call PUT endpoint with correct data', async () => {
const caseId = 'test-case-001';
const reviewData = {
review_status: 'reviewed' as const,
reviewed_by: 'Dr. Test',
review_notes: 'Test notes',
priority: 'high' as const,
};
mockApi.put.mockResolvedValue(mockResponse);
await aiPredictionAPI.updateCaseReview(caseId, reviewData, mockToken);
expect(mockApi.put).toHaveBeenCalledWith(
`/api/ai-cases/review/${caseId}`,
reviewData,
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
});
// ============================================================================
// GET STATISTICS TESTS
// ============================================================================
describe('getPredictionStats', () => {
it('should call GET endpoint with time range parameter', async () => {
mockApi.get.mockResolvedValue(mockResponse);
await aiPredictionAPI.getPredictionStats(mockToken, 'week');
expect(mockApi.get).toHaveBeenCalledWith(
'/api/ai-cases/statistics',
{ timeRange: 'week' },
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
it('should call GET endpoint without time range parameter', async () => {
mockApi.get.mockResolvedValue(mockResponse);
await aiPredictionAPI.getPredictionStats(mockToken);
expect(mockApi.get).toHaveBeenCalledWith(
'/api/ai-cases/statistics',
{},
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
});
// ============================================================================
// SEARCH PREDICTIONS TESTS
// ============================================================================
describe('searchPredictions', () => {
it('should call GET endpoint with search query and filters', async () => {
const query = 'test search';
const filters = {
urgency: ['urgent', 'emergency'],
severity: ['high'],
dateRange: { start: '2024-01-01', end: '2024-01-31' },
};
mockApi.get.mockResolvedValue(mockResponse);
await aiPredictionAPI.searchPredictions(query, mockToken, filters);
expect(mockApi.get).toHaveBeenCalledWith(
'/api/ai-cases/search',
{
q: query,
filters: JSON.stringify(filters),
},
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
it('should call GET endpoint with only search query', async () => {
const query = 'test search';
mockApi.get.mockResolvedValue(mockResponse);
await aiPredictionAPI.searchPredictions(query, mockToken);
expect(mockApi.get).toHaveBeenCalledWith(
'/api/ai-cases/search',
{ q: query },
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
});
// ============================================================================
// BULK OPERATIONS TESTS
// ============================================================================
describe('bulkUpdateReviews', () => {
it('should call PUT endpoint with case IDs and review data', async () => {
const caseIds = ['case-001', 'case-002', 'case-003'];
const reviewData = {
review_status: 'reviewed' as const,
reviewed_by: 'Dr. Test',
review_notes: 'Bulk review',
};
mockApi.put.mockResolvedValue(mockResponse);
await aiPredictionAPI.bulkUpdateReviews(caseIds, reviewData, mockToken);
expect(mockApi.put).toHaveBeenCalledWith(
'/api/ai-cases/bulk-review',
{ caseIds, reviewData },
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
});
// ============================================================================
// SUBMIT FEEDBACK TESTS
// ============================================================================
describe('submitPredictionFeedback', () => {
it('should call POST endpoint with feedback data', async () => {
const caseId = 'test-case-001';
const feedbackData = {
accuracy_rating: 4 as const,
is_accurate: true,
physician_diagnosis: 'Confirmed midline shift',
feedback_notes: 'Accurate prediction',
improvement_suggestions: 'None',
};
mockApi.post.mockResolvedValue(mockResponse);
await aiPredictionAPI.submitPredictionFeedback(caseId, feedbackData, mockToken);
expect(mockApi.post).toHaveBeenCalledWith(
`/api/ai-cases/feedback/${caseId}`,
feedbackData,
expect.objectContaining({
headers: expect.objectContaining({
Authorization: `Bearer ${mockToken}`,
}),
})
);
});
});
// ============================================================================
// ERROR HANDLING TESTS
// ============================================================================
describe('Error handling', () => {
it('should handle API errors gracefully', async () => {
const errorResponse = {
ok: false,
problem: 'NETWORK_ERROR',
data: null,
};
mockApi.get.mockResolvedValue(errorResponse);
const result = await aiPredictionAPI.getAllPredictions(mockToken);
expect(result).toEqual(errorResponse);
});
it('should handle missing token', async () => {
mockApi.get.mockResolvedValue(mockResponse);
await aiPredictionAPI.getAllPredictions('');
expect(mockApi.get).toHaveBeenCalledWith(
'/api/ai-cases/all-prediction-results',
{},
expect.objectContaining({
headers: expect.objectContaining({
'Content-Type': 'application/json',
}),
})
);
});
});
});
/*
* End of File: aiPredictionAPI.test.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,231 @@
/*
* File: aiPredictionSlice.test.ts
* Description: Unit tests for AI Prediction Redux slice
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import aiPredictionReducer, {
setSearchQuery,
setUrgencyFilter,
setSeverityFilter,
setCategoryFilter,
clearAllFilters,
toggleShowFilters,
clearError,
} from '../redux/aiPredictionSlice';
import type { AIPredictionState } from '../types';
// ============================================================================
// MOCK DATA
// ============================================================================
const initialState: AIPredictionState = {
predictionCases: [],
currentCase: null,
isLoading: false,
isRefreshing: false,
isLoadingCaseDetails: false,
error: null,
searchQuery: '',
selectedUrgencyFilter: 'all',
selectedSeverityFilter: 'all',
selectedCategoryFilter: 'all',
sortBy: 'date',
sortOrder: 'desc',
currentPage: 1,
itemsPerPage: 20,
totalItems: 0,
lastUpdated: null,
cacheExpiry: null,
showFilters: false,
selectedCaseIds: [],
};
// ============================================================================
// UNIT TESTS
// ============================================================================
describe('AI Prediction Slice', () => {
// ============================================================================
// INITIAL STATE TESTS
// ============================================================================
it('should return the initial state', () => {
const result = aiPredictionReducer(undefined, { type: 'unknown' });
expect(result).toEqual(initialState);
});
// ============================================================================
// SEARCH TESTS
// ============================================================================
describe('Search functionality', () => {
it('should handle setSearchQuery', () => {
const searchQuery = 'test search';
const action = setSearchQuery(searchQuery);
const result = aiPredictionReducer(initialState, action);
expect(result.searchQuery).toBe(searchQuery);
expect(result.currentPage).toBe(1); // Should reset to first page
});
it('should handle empty search query', () => {
const state = { ...initialState, searchQuery: 'existing search' };
const action = setSearchQuery('');
const result = aiPredictionReducer(state, action);
expect(result.searchQuery).toBe('');
expect(result.currentPage).toBe(1);
});
});
// ============================================================================
// FILTER TESTS
// ============================================================================
describe('Filter functionality', () => {
it('should handle setUrgencyFilter', () => {
const filter = 'urgent';
const action = setUrgencyFilter(filter);
const result = aiPredictionReducer(initialState, action);
expect(result.selectedUrgencyFilter).toBe(filter);
expect(result.currentPage).toBe(1); // Should reset to first page
});
it('should handle setSeverityFilter', () => {
const filter = 'high';
const action = setSeverityFilter(filter);
const result = aiPredictionReducer(initialState, action);
expect(result.selectedSeverityFilter).toBe(filter);
expect(result.currentPage).toBe(1);
});
it('should handle setCategoryFilter', () => {
const filter = 'critical';
const action = setCategoryFilter(filter);
const result = aiPredictionReducer(initialState, action);
expect(result.selectedCategoryFilter).toBe(filter);
expect(result.currentPage).toBe(1);
});
it('should handle clearAllFilters', () => {
const state: AIPredictionState = {
...initialState,
searchQuery: 'test',
selectedUrgencyFilter: 'urgent',
selectedSeverityFilter: 'high',
selectedCategoryFilter: 'critical',
currentPage: 3,
};
const action = clearAllFilters();
const result = aiPredictionReducer(state, action);
expect(result.searchQuery).toBe('');
expect(result.selectedUrgencyFilter).toBe('all');
expect(result.selectedSeverityFilter).toBe('all');
expect(result.selectedCategoryFilter).toBe('all');
expect(result.currentPage).toBe(1);
});
});
// ============================================================================
// UI STATE TESTS
// ============================================================================
describe('UI state functionality', () => {
it('should handle toggleShowFilters', () => {
const action = toggleShowFilters();
// Toggle from false to true
const result1 = aiPredictionReducer(initialState, action);
expect(result1.showFilters).toBe(true);
// Toggle from true to false
const result2 = aiPredictionReducer(result1, action);
expect(result2.showFilters).toBe(false);
});
it('should handle clearError', () => {
const state = { ...initialState, error: 'Test error' };
const action = clearError();
const result = aiPredictionReducer(state, action);
expect(result.error).toBe(null);
});
});
// ============================================================================
// ASYNC ACTION TESTS
// ============================================================================
describe('Async actions', () => {
it('should handle fetchAIPredictions.pending', () => {
const action = { type: 'aiPrediction/fetchAIPredictions/pending' };
const result = aiPredictionReducer(initialState, action);
expect(result.isLoading).toBe(true);
expect(result.error).toBe(null);
});
it('should handle fetchAIPredictions.fulfilled', () => {
const mockCases = [
{
patid: 'test-001',
hospital_id: 'hospital-001',
prediction: {
label: 'test finding',
finding_type: 'pathology' as const,
clinical_urgency: 'urgent' as const,
confidence_score: 0.95,
finding_category: 'abnormal' as const,
primary_severity: 'high' as const,
anatomical_location: 'brain',
},
},
];
const action = {
type: 'aiPrediction/fetchAIPredictions/fulfilled',
payload: {
cases: mockCases,
total: 1,
page: 1,
limit: 20,
},
};
const result = aiPredictionReducer(initialState, action);
expect(result.isLoading).toBe(false);
expect(result.predictionCases).toEqual(mockCases);
expect(result.totalItems).toBe(1);
expect(result.error).toBe(null);
expect(result.lastUpdated).toBeInstanceOf(Date);
expect(result.cacheExpiry).toBeInstanceOf(Date);
});
it('should handle fetchAIPredictions.rejected', () => {
const errorMessage = 'Failed to fetch predictions';
const action = {
type: 'aiPrediction/fetchAIPredictions/rejected',
payload: errorMessage,
};
const result = aiPredictionReducer(initialState, action);
expect(result.isLoading).toBe(false);
expect(result.error).toBe(errorMessage);
});
});
});
/*
* End of File: aiPredictionSlice.test.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,522 @@
/*
* File: AIPredictionCard.tsx
* Description: Card component for displaying AI prediction case information
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Dimensions,
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme';
import { AIPredictionCase, URGENCY_COLORS, SEVERITY_COLORS, CATEGORY_COLORS } from '../types';
// ============================================================================
// INTERFACES
// ============================================================================
interface AIPredictionCardProps {
predictionCase: AIPredictionCase;
onPress: (predictionCase: AIPredictionCase) => void;
onReview?: (caseId: string) => void;
isSelected?: boolean;
onToggleSelect?: (caseId: string) => void;
showReviewButton?: boolean;
}
// ============================================================================
// CONSTANTS
// ============================================================================
const { width } = Dimensions.get('window');
const CARD_WIDTH = width - 32; // Full width with margins
// ============================================================================
// AI PREDICTION CARD COMPONENT
// ============================================================================
/**
* AIPredictionCard Component
*
* Purpose: Display AI prediction case information in a card format
*
* Features:
* - Patient ID and hospital information
* - AI prediction results with confidence score
* - Urgency and severity indicators
* - Review status and actions
* - Selection support for bulk operations
* - Modern card design with proper spacing
* - Color-coded priority indicators
* - Accessibility support
*/
const AIPredictionCard: React.FC<AIPredictionCardProps> = ({
predictionCase,
onPress,
onReview,
isSelected = false,
onToggleSelect,
showReviewButton = true,
}) => {
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
/**
* Get Urgency Color
*
* Purpose: Get color based on clinical urgency
*/
const getUrgencyColor = (urgency: string): string => {
return URGENCY_COLORS[urgency as keyof typeof URGENCY_COLORS] || theme.colors.textMuted;
};
/**
* Get Severity Color
*
* Purpose: Get color based on primary severity
*/
const getSeverityColor = (severity: string): string => {
return SEVERITY_COLORS[severity as keyof typeof SEVERITY_COLORS] || theme.colors.textMuted;
};
/**
* Get Category Color
*
* Purpose: Get color based on finding category
*/
const getCategoryColor = (category: string): string => {
return CATEGORY_COLORS[category as keyof typeof CATEGORY_COLORS] || theme.colors.textMuted;
};
/**
* Get Review Status Color
*
* Purpose: Get color based on review status
*/
const getReviewStatusColor = (status: string): string => {
switch (status) {
case 'confirmed':
return theme.colors.success;
case 'reviewed':
return theme.colors.info;
case 'disputed':
return theme.colors.warning;
case 'pending':
default:
return theme.colors.error;
}
};
/**
* Format Confidence Score
*
* Purpose: Format confidence score as percentage
*/
const formatConfidence = (score: number): string => {
return `${Math.round(score * 100)}%`;
};
/**
* Capitalize Text
*
* Purpose: Capitalize first letter of each word
*/
const capitalize = (text: string): string => {
return text.split('_').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ');
};
/**
* Format Date
*
* Purpose: Format date for display
*/
const formatDate = (dateString?: string): string => {
if (!dateString) return 'N/A';
try {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
} catch {
return 'N/A';
}
};
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Handle Card Press
*
* Purpose: Handle card tap to view details
*/
const handleCardPress = () => {
onPress(predictionCase);
};
/**
* Handle Review Press
*
* Purpose: Handle review button press
*/
const handleReviewPress = (event: any) => {
event.stopPropagation();
if (onReview) {
onReview(predictionCase.patid);
}
};
/**
* Handle Selection Toggle
*
* Purpose: Handle case selection toggle
*/
const handleSelectionToggle = (event: any) => {
event.stopPropagation();
if (onToggleSelect) {
onToggleSelect(predictionCase.patid);
}
};
// ============================================================================
// RENDER
// ============================================================================
return (
<TouchableOpacity
style={[
styles.container,
isSelected && styles.selectedContainer,
predictionCase.prediction.clinical_urgency === 'emergency' && styles.emergencyContainer,
]}
onPress={handleCardPress}
activeOpacity={0.7}
accessibilityRole="button"
accessibilityLabel={`AI Prediction case for patient ${predictionCase.patid}`}
accessibilityHint="Tap to view detailed prediction information"
>
{/* Header Section */}
<View style={styles.header}>
<View style={styles.headerLeft}>
<Text style={styles.patientId} numberOfLines={1}>
{predictionCase.patid}
</Text>
<Text style={styles.date}>
{formatDate(predictionCase.processed_at)}
</Text>
</View>
<View style={styles.headerRight}>
{onToggleSelect && (
<TouchableOpacity
style={styles.selectionButton}
onPress={handleSelectionToggle}
accessibilityRole="checkbox"
accessibilityState={{ checked: isSelected }}
>
<Icon
name={isSelected ? 'check-square' : 'square'}
size={20}
color={isSelected ? theme.colors.primary : theme.colors.textMuted}
/>
</TouchableOpacity>
)}
<View style={[
styles.priorityBadge,
{ backgroundColor: getUrgencyColor(predictionCase.prediction.clinical_urgency) }
]}>
<Text style={styles.priorityText}>
{capitalize(predictionCase.prediction.clinical_urgency)}
</Text>
</View>
</View>
</View>
{/* Prediction Information */}
<View style={styles.predictionSection}>
<View style={styles.predictionHeader}>
<Text style={styles.predictionLabel} numberOfLines={2}>
{capitalize(predictionCase.prediction.label)}
</Text>
<View style={styles.confidenceContainer}>
<Icon name="trending-up" size={16} color={theme.colors.primary} />
<Text style={styles.confidenceText}>
{formatConfidence(predictionCase.prediction.confidence_score)}
</Text>
</View>
</View>
{/* Finding Details */}
<View style={styles.findingDetails}>
<View style={styles.findingItem}>
<Text style={styles.findingLabel}>Type:</Text>
<Text style={styles.findingValue}>
{capitalize(predictionCase.prediction.finding_type)}
</Text>
</View>
<View style={styles.findingItem}>
<Text style={styles.findingLabel}>Category:</Text>
<View style={[
styles.categoryBadge,
{ backgroundColor: getCategoryColor(predictionCase.prediction.finding_category) }
]}>
<Text style={styles.categoryText}>
{capitalize(predictionCase.prediction.finding_category)}
</Text>
</View>
</View>
</View>
{/* Severity and Location */}
<View style={styles.detailsRow}>
<View style={styles.detailItem}>
<Icon name="alert-triangle" size={14} color={getSeverityColor(predictionCase.prediction.primary_severity)} />
<Text style={[styles.detailText, { color: getSeverityColor(predictionCase.prediction.primary_severity) }]}>
{capitalize(predictionCase.prediction.primary_severity)} Severity
</Text>
</View>
{predictionCase.prediction.anatomical_location !== 'not_applicable' && (
<View style={styles.detailItem}>
<Icon name="map-pin" size={14} color={theme.colors.textSecondary} />
<Text style={styles.detailText}>
{capitalize(predictionCase.prediction.anatomical_location)}
</Text>
</View>
)}
</View>
</View>
{/* Footer Section */}
<View style={styles.footer}>
<View style={styles.footerLeft}>
<View style={[
styles.reviewStatusBadge,
{ backgroundColor: getReviewStatusColor(predictionCase.review_status || 'pending') }
]}>
<Text style={styles.reviewStatusText}>
{capitalize(predictionCase.review_status || 'pending')}
</Text>
</View>
{predictionCase.reviewed_by && (
<Text style={styles.reviewedBy}>
by {predictionCase.reviewed_by}
</Text>
)}
</View>
{showReviewButton && predictionCase.review_status === 'pending' && (
<TouchableOpacity
style={styles.reviewButton}
onPress={handleReviewPress}
accessibilityRole="button"
accessibilityLabel="Review this case"
>
<Icon name="eye" size={16} color={theme.colors.primary} />
<Text style={styles.reviewButtonText}>Review</Text>
</TouchableOpacity>
)}
</View>
</TouchableOpacity>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.large,
padding: theme.spacing.lg,
marginHorizontal: theme.spacing.md,
marginVertical: theme.spacing.sm,
width: CARD_WIDTH,
...theme.shadows.medium,
borderWidth: 1,
borderColor: theme.colors.border,
},
selectedContainer: {
borderColor: theme.colors.primary,
borderWidth: 2,
},
emergencyContainer: {
borderLeftWidth: 4,
borderLeftColor: URGENCY_COLORS.emergency,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: theme.spacing.md,
},
headerLeft: {
flex: 1,
},
headerRight: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.sm,
},
patientId: {
fontSize: theme.typography.fontSize.bodyLarge,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
marginBottom: theme.spacing.xs,
},
date: {
fontSize: theme.typography.fontSize.bodySmall,
color: theme.colors.textSecondary,
},
selectionButton: {
padding: theme.spacing.xs,
},
priorityBadge: {
paddingHorizontal: theme.spacing.sm,
paddingVertical: theme.spacing.xs,
borderRadius: theme.borderRadius.small,
},
priorityText: {
fontSize: theme.typography.fontSize.caption,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.background,
},
predictionSection: {
marginBottom: theme.spacing.md,
},
predictionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: theme.spacing.sm,
},
predictionLabel: {
fontSize: theme.typography.fontSize.bodyLarge,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.textPrimary,
flex: 1,
marginRight: theme.spacing.sm,
},
confidenceContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.xs,
},
confidenceText: {
fontSize: theme.typography.fontSize.bodyMedium,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.primary,
},
findingDetails: {
marginBottom: theme.spacing.sm,
},
findingItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: theme.spacing.xs,
},
findingLabel: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
fontWeight: theme.typography.fontWeight.medium,
},
findingValue: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textPrimary,
},
categoryBadge: {
paddingHorizontal: theme.spacing.sm,
paddingVertical: theme.spacing.xs,
borderRadius: theme.borderRadius.small,
},
categoryText: {
fontSize: theme.typography.fontSize.caption,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.background,
},
detailsRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
detailItem: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.xs,
flex: 1,
},
detailText: {
fontSize: theme.typography.fontSize.bodySmall,
color: theme.colors.textSecondary,
},
footer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingTop: theme.spacing.sm,
borderTopWidth: 1,
borderTopColor: theme.colors.border,
},
footerLeft: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.sm,
flex: 1,
},
reviewStatusBadge: {
paddingHorizontal: theme.spacing.sm,
paddingVertical: theme.spacing.xs,
borderRadius: theme.borderRadius.small,
},
reviewStatusText: {
fontSize: theme.typography.fontSize.caption,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.background,
},
reviewedBy: {
fontSize: theme.typography.fontSize.caption,
color: theme.colors.textMuted,
fontStyle: 'italic',
},
reviewButton: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.xs,
paddingHorizontal: theme.spacing.sm,
paddingVertical: theme.spacing.xs,
borderRadius: theme.borderRadius.small,
borderWidth: 1,
borderColor: theme.colors.primary,
},
reviewButtonText: {
fontSize: theme.typography.fontSize.bodySmall,
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.medium,
},
});
export default AIPredictionCard;
/*
* End of File: AIPredictionCard.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,287 @@
/*
* File: EmptyState.tsx
* Description: Empty state component for AI predictions
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Dimensions,
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme';
// ============================================================================
// INTERFACES
// ============================================================================
interface EmptyStateProps {
title?: string;
message?: string;
iconName?: string;
actionText?: string;
onAction?: () => void;
style?: any;
showRefreshButton?: boolean;
onRefresh?: () => void;
}
// ============================================================================
// CONSTANTS
// ============================================================================
const { width, height } = Dimensions.get('window');
// ============================================================================
// EMPTY STATE COMPONENT
// ============================================================================
/**
* EmptyState Component
*
* Purpose: Display empty state for AI predictions
*
* Features:
* - Customizable title and message
* - Icon display with customizable icon
* - Optional action button
* - Refresh functionality
* - Responsive design
* - Modern empty state design
* - Accessibility support
*/
const EmptyState: React.FC<EmptyStateProps> = ({
title = 'No AI Predictions Found',
message = 'There are no AI prediction cases available at the moment. Try adjusting your filters or refresh to see new predictions.',
iconName = 'brain',
actionText = 'Refresh',
onAction,
style,
showRefreshButton = true,
onRefresh,
}) => {
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Handle Action Press
*
* Purpose: Handle action button press
*/
const handleActionPress = () => {
if (onAction) {
onAction();
} else if (onRefresh) {
onRefresh();
}
};
// ============================================================================
// RENDER
// ============================================================================
return (
<View style={[styles.container, style]}>
{/* Empty State Icon */}
<View style={styles.iconContainer}>
<Icon
name={iconName}
size={64}
color={theme.colors.textMuted}
style={styles.icon}
/>
</View>
{/* Empty State Title */}
<Text style={styles.title} accessibilityRole="header">
{title}
</Text>
{/* Empty State Message */}
<Text style={styles.message}>
{message}
</Text>
{/* Action Buttons */}
<View style={styles.buttonsContainer}>
{/* Primary Action Button */}
{(onAction || onRefresh) && (
<TouchableOpacity
style={styles.actionButton}
onPress={handleActionPress}
accessibilityRole="button"
accessibilityLabel={actionText}
>
<Icon
name="refresh-cw"
size={18}
color={theme.colors.background}
style={styles.buttonIcon}
/>
<Text style={styles.actionButtonText}>
{actionText}
</Text>
</TouchableOpacity>
)}
{/* Secondary Refresh Button */}
{showRefreshButton && onRefresh && !onAction && (
<TouchableOpacity
style={styles.secondaryButton}
onPress={onRefresh}
accessibilityRole="button"
accessibilityLabel="Refresh data"
>
<Icon
name="refresh-cw"
size={16}
color={theme.colors.primary}
style={styles.buttonIcon}
/>
<Text style={styles.secondaryButtonText}>
Refresh Data
</Text>
</TouchableOpacity>
)}
</View>
{/* Suggestions */}
<View style={styles.suggestionsContainer}>
<Text style={styles.suggestionsTitle}>Try:</Text>
<View style={styles.suggestionsList}>
<View style={styles.suggestionItem}>
<Icon name="search" size={14} color={theme.colors.textMuted} />
<Text style={styles.suggestionText}>Clearing search filters</Text>
</View>
<View style={styles.suggestionItem}>
<Icon name="filter" size={14} color={theme.colors.textMuted} />
<Text style={styles.suggestionText}>Adjusting filter criteria</Text>
</View>
<View style={styles.suggestionItem}>
<Icon name="refresh-cw" size={14} color={theme.colors.textMuted} />
<Text style={styles.suggestionText}>Refreshing the data</Text>
</View>
</View>
</View>
</View>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: theme.spacing.xl,
paddingVertical: theme.spacing.xxl,
minHeight: height * 0.4,
},
iconContainer: {
width: 120,
height: 120,
borderRadius: 60,
backgroundColor: theme.colors.backgroundAlt,
justifyContent: 'center',
alignItems: 'center',
marginBottom: theme.spacing.xl,
...theme.shadows.small,
},
icon: {
opacity: 0.6,
},
title: {
fontSize: theme.typography.fontSize.displaySmall,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
textAlign: 'center',
marginBottom: theme.spacing.md,
},
message: {
fontSize: theme.typography.fontSize.bodyLarge,
color: theme.colors.textSecondary,
textAlign: 'center',
lineHeight: theme.typography.lineHeight.relaxed * theme.typography.fontSize.bodyLarge,
marginBottom: theme.spacing.xl,
maxWidth: width * 0.8,
},
buttonsContainer: {
flexDirection: 'row',
gap: theme.spacing.md,
marginBottom: theme.spacing.xl,
},
actionButton: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.colors.primary,
paddingHorizontal: theme.spacing.lg,
paddingVertical: theme.spacing.md,
borderRadius: theme.borderRadius.medium,
gap: theme.spacing.sm,
...theme.shadows.medium,
},
actionButtonText: {
fontSize: theme.typography.fontSize.bodyMedium,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.background,
},
secondaryButton: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'transparent',
borderWidth: 1,
borderColor: theme.colors.primary,
paddingHorizontal: theme.spacing.lg,
paddingVertical: theme.spacing.md,
borderRadius: theme.borderRadius.medium,
gap: theme.spacing.sm,
},
secondaryButtonText: {
fontSize: theme.typography.fontSize.bodyMedium,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.primary,
},
buttonIcon: {
// No additional styles needed
},
suggestionsContainer: {
alignItems: 'center',
maxWidth: width * 0.8,
},
suggestionsTitle: {
fontSize: theme.typography.fontSize.bodyMedium,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.textSecondary,
marginBottom: theme.spacing.sm,
},
suggestionsList: {
gap: theme.spacing.sm,
},
suggestionItem: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.sm,
paddingVertical: theme.spacing.xs,
},
suggestionText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textMuted,
},
});
export default EmptyState;
/*
* End of File: EmptyState.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,368 @@
/*
* File: FilterTabs.tsx
* Description: Filter tabs component for AI predictions
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
ScrollView,
Dimensions,
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme';
import type { AIPredictionState } from '../types';
// ============================================================================
// INTERFACES
// ============================================================================
interface FilterTabsProps {
selectedUrgencyFilter: AIPredictionState['selectedUrgencyFilter'];
selectedSeverityFilter: AIPredictionState['selectedSeverityFilter'];
selectedCategoryFilter: AIPredictionState['selectedCategoryFilter'];
onUrgencyFilterChange: (filter: AIPredictionState['selectedUrgencyFilter']) => void;
onSeverityFilterChange: (filter: AIPredictionState['selectedSeverityFilter']) => void;
onCategoryFilterChange: (filter: AIPredictionState['selectedCategoryFilter']) => void;
onClearFilters: () => void;
filterCounts?: {
urgency: Record<string, number>;
severity: Record<string, number>;
category: Record<string, number>;
};
activeFiltersCount?: number;
}
interface FilterOption {
label: string;
value: string;
count?: number;
color?: string;
}
// ============================================================================
// CONSTANTS
// ============================================================================
const { width } = Dimensions.get('window');
const URGENCY_FILTERS: FilterOption[] = [
{ label: 'All', value: 'all' },
{ label: 'Emergency', value: 'emergency', color: '#F44336' },
{ label: 'Urgent', value: 'urgent', color: '#FF5722' },
{ label: 'Moderate', value: 'moderate', color: '#FF9800' },
{ label: 'Low', value: 'low', color: '#FFC107' },
{ label: 'Routine', value: 'routine', color: '#4CAF50' },
];
const SEVERITY_FILTERS: FilterOption[] = [
{ label: 'All', value: 'all' },
{ label: 'High', value: 'high', color: '#F44336' },
{ label: 'Medium', value: 'medium', color: '#FF9800' },
{ label: 'Low', value: 'low', color: '#FFC107' },
{ label: 'None', value: 'none', color: '#4CAF50' },
];
const CATEGORY_FILTERS: FilterOption[] = [
{ label: 'All', value: 'all' },
{ label: 'Critical', value: 'critical', color: '#F44336' },
{ label: 'Abnormal', value: 'abnormal', color: '#FF9800' },
{ label: 'Warning', value: 'warning', color: '#FFC107' },
{ label: 'Normal', value: 'normal', color: '#4CAF50' },
{ label: 'Unknown', value: 'unknown', color: '#9E9E9E' },
];
// ============================================================================
// FILTER TABS COMPONENT
// ============================================================================
/**
* FilterTabs Component
*
* Purpose: Provide filtering functionality for AI predictions
*
* Features:
* - Multiple filter categories (urgency, severity, category)
* - Visual filter counts
* - Active filter indicators
* - Clear all filters functionality
* - Color-coded filter options
* - Horizontal scroll support
* - Responsive design
* - Accessibility support
*/
const FilterTabs: React.FC<FilterTabsProps> = ({
selectedUrgencyFilter,
selectedSeverityFilter,
selectedCategoryFilter,
onUrgencyFilterChange,
onSeverityFilterChange,
onCategoryFilterChange,
onClearFilters,
filterCounts,
activeFiltersCount = 0,
}) => {
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
/**
* Get Filter Count
*
* Purpose: Get count for specific filter value
*/
const getFilterCount = (category: 'urgency' | 'severity' | 'category', value: string): number => {
return filterCounts?.[category]?.[value] || 0;
};
/**
* Render Filter Tab
*
* Purpose: Render individual filter tab
*/
const renderFilterTab = (
option: FilterOption,
isSelected: boolean,
onPress: () => void,
category: 'urgency' | 'severity' | 'category'
) => {
const count = getFilterCount(category, option.value);
return (
<TouchableOpacity
key={option.value}
style={[
styles.filterTab,
isSelected && styles.selectedFilterTab,
isSelected && option.color && { borderColor: option.color },
]}
onPress={onPress}
accessibilityRole="button"
accessibilityState={{ selected: isSelected }}
accessibilityLabel={`Filter by ${option.label}${count > 0 ? `, ${count} items` : ''}`}
>
{option.color && isSelected && (
<View style={[styles.colorIndicator, { backgroundColor: option.color }]} />
)}
<Text style={[
styles.filterTabText,
isSelected && styles.selectedFilterTabText,
isSelected && option.color && { color: option.color },
]}>
{option.label}
</Text>
{count > 0 && (
<View style={[
styles.countBadge,
isSelected && styles.selectedCountBadge,
isSelected && option.color && { backgroundColor: option.color },
]}>
<Text style={[
styles.countText,
isSelected && styles.selectedCountText,
]}>
{count}
</Text>
</View>
)}
</TouchableOpacity>
);
};
// ============================================================================
// RENDER
// ============================================================================
return (
<View style={styles.container}>
{/* Header with Clear Filters */}
<View style={styles.header}>
<Text style={styles.headerTitle}>Filters</Text>
{activeFiltersCount > 0 && (
<TouchableOpacity
style={styles.clearButton}
onPress={onClearFilters}
accessibilityRole="button"
accessibilityLabel="Clear all filters"
>
<Icon name="x" size={16} color={theme.colors.primary} />
<Text style={styles.clearButtonText}>Clear All</Text>
</TouchableOpacity>
)}
</View>
{/* Urgency Filters */}
<View style={styles.filterSection}>
<Text style={styles.sectionTitle}>Clinical Urgency</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.filterRow}
>
{URGENCY_FILTERS.map((option) =>
renderFilterTab(
{ ...option, count: getFilterCount('urgency', option.value) },
selectedUrgencyFilter === option.value,
() => onUrgencyFilterChange(option.value as AIPredictionState['selectedUrgencyFilter']),
'urgency'
)
)}
</ScrollView>
</View>
{/* Severity Filters */}
<View style={styles.filterSection}>
<Text style={styles.sectionTitle}>Primary Severity</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.filterRow}
>
{SEVERITY_FILTERS.map((option) =>
renderFilterTab(
{ ...option, count: getFilterCount('severity', option.value) },
selectedSeverityFilter === option.value,
() => onSeverityFilterChange(option.value as AIPredictionState['selectedSeverityFilter']),
'severity'
)
)}
</ScrollView>
</View>
{/* Category Filters */}
<View style={styles.filterSection}>
<Text style={styles.sectionTitle}>Finding Category</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.filterRow}
>
{CATEGORY_FILTERS.map((option) =>
renderFilterTab(
{ ...option, count: getFilterCount('category', option.value) },
selectedCategoryFilter === option.value,
() => onCategoryFilterChange(option.value as AIPredictionState['selectedCategoryFilter']),
'category'
)
)}
</ScrollView>
</View>
</View>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
backgroundColor: theme.colors.background,
paddingVertical: theme.spacing.md,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
marginBottom: theme.spacing.md,
},
headerTitle: {
fontSize: theme.typography.fontSize.bodyLarge,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
clearButton: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.xs,
paddingHorizontal: theme.spacing.sm,
paddingVertical: theme.spacing.xs,
borderRadius: theme.borderRadius.small,
borderWidth: 1,
borderColor: theme.colors.primary,
},
clearButtonText: {
fontSize: theme.typography.fontSize.bodySmall,
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.medium,
},
filterSection: {
marginBottom: theme.spacing.lg,
},
sectionTitle: {
fontSize: theme.typography.fontSize.bodyMedium,
fontWeight: theme.typography.fontWeight.medium,
color: theme.colors.textSecondary,
paddingHorizontal: theme.spacing.md,
marginBottom: theme.spacing.sm,
},
filterRow: {
paddingHorizontal: theme.spacing.md,
gap: theme.spacing.sm,
},
filterTab: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
borderRadius: theme.borderRadius.medium,
borderWidth: 1,
borderColor: theme.colors.border,
backgroundColor: theme.colors.background,
gap: theme.spacing.xs,
},
selectedFilterTab: {
borderColor: theme.colors.primary,
backgroundColor: theme.colors.backgroundAccent,
},
colorIndicator: {
width: 8,
height: 8,
borderRadius: 4,
},
filterTabText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
fontWeight: theme.typography.fontWeight.medium,
},
selectedFilterTabText: {
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.bold,
},
countBadge: {
backgroundColor: theme.colors.textMuted,
borderRadius: theme.borderRadius.small,
paddingHorizontal: theme.spacing.xs,
paddingVertical: 2,
minWidth: 20,
alignItems: 'center',
},
selectedCountBadge: {
backgroundColor: theme.colors.primary,
},
countText: {
fontSize: theme.typography.fontSize.caption,
color: theme.colors.background,
fontWeight: theme.typography.fontWeight.bold,
},
selectedCountText: {
color: theme.colors.background,
},
});
export default FilterTabs;
/*
* End of File: FilterTabs.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,139 @@
/*
* File: LoadingState.tsx
* Description: Loading state component for AI predictions
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
ActivityIndicator,
Dimensions,
} from 'react-native';
import { theme } from '../../../theme';
// ============================================================================
// INTERFACES
// ============================================================================
interface LoadingStateProps {
message?: string;
showSpinner?: boolean;
size?: 'small' | 'large';
style?: any;
}
// ============================================================================
// CONSTANTS
// ============================================================================
const { width, height } = Dimensions.get('window');
// ============================================================================
// LOADING STATE COMPONENT
// ============================================================================
/**
* LoadingState Component
*
* Purpose: Display loading state for AI predictions
*
* Features:
* - Customizable loading message
* - Optional spinner display
* - Different spinner sizes
* - Custom styling support
* - Centered layout
* - Accessibility support
*/
const LoadingState: React.FC<LoadingStateProps> = ({
message = 'Loading AI predictions...',
showSpinner = true,
size = 'large',
style,
}) => {
// ============================================================================
// RENDER
// ============================================================================
return (
<View style={[styles.container, style]} accessibilityRole="progressbar">
{/* Loading Spinner */}
{showSpinner && (
<ActivityIndicator
size={size}
color={theme.colors.primary}
style={styles.spinner}
/>
)}
{/* Loading Message */}
<Text style={styles.message} accessibilityLabel={message}>
{message}
</Text>
{/* Loading Animation Dots */}
<View style={styles.dotsContainer}>
<View style={[styles.dot, styles.dot1]} />
<View style={[styles.dot, styles.dot2]} />
<View style={[styles.dot, styles.dot3]} />
</View>
</View>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: theme.spacing.xl,
paddingVertical: theme.spacing.xxl,
minHeight: height * 0.3,
},
spinner: {
marginBottom: theme.spacing.lg,
},
message: {
fontSize: theme.typography.fontSize.bodyLarge,
color: theme.colors.textSecondary,
textAlign: 'center',
fontWeight: theme.typography.fontWeight.medium,
marginBottom: theme.spacing.xl,
},
dotsContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.sm,
},
dot: {
width: 8,
height: 8,
borderRadius: 4,
backgroundColor: theme.colors.primary,
},
dot1: {
opacity: 0.3,
},
dot2: {
opacity: 0.6,
},
dot3: {
opacity: 1,
},
});
export default LoadingState;
/*
* End of File: LoadingState.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,226 @@
/*
* File: SearchBar.tsx
* Description: Search bar component for filtering AI predictions
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useState, useCallback } from 'react';
import {
View,
TextInput,
StyleSheet,
TouchableOpacity,
Dimensions,
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme';
// ============================================================================
// INTERFACES
// ============================================================================
interface SearchBarProps {
value: string;
onChangeText: (text: string) => void;
onClear?: () => void;
placeholder?: string;
autoFocus?: boolean;
disabled?: boolean;
}
// ============================================================================
// CONSTANTS
// ============================================================================
const { width } = Dimensions.get('window');
// ============================================================================
// SEARCH BAR COMPONENT
// ============================================================================
/**
* SearchBar Component
*
* Purpose: Provide search functionality for AI predictions
*
* Features:
* - Real-time search input
* - Clear button functionality
* - Customizable placeholder text
* - Auto-focus support
* - Disabled state support
* - Modern design with icons
* - Responsive width
* - Accessibility support
*/
const SearchBar: React.FC<SearchBarProps> = ({
value,
onChangeText,
onClear,
placeholder = 'Search predictions...',
autoFocus = false,
disabled = false,
}) => {
// ============================================================================
// STATE
// ============================================================================
const [isFocused, setIsFocused] = useState(false);
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Handle Focus
*
* Purpose: Handle input focus state
*/
const handleFocus = useCallback(() => {
setIsFocused(true);
}, []);
/**
* Handle Blur
*
* Purpose: Handle input blur state
*/
const handleBlur = useCallback(() => {
setIsFocused(false);
}, []);
/**
* Handle Clear
*
* Purpose: Clear search input
*/
const handleClear = useCallback(() => {
onChangeText('');
if (onClear) {
onClear();
}
}, [onChangeText, onClear]);
/**
* Handle Text Change
*
* Purpose: Handle search text input
*/
const handleTextChange = useCallback((text: string) => {
onChangeText(text);
}, [onChangeText]);
// ============================================================================
// RENDER
// ============================================================================
return (
<View style={[
styles.container,
isFocused && styles.focusedContainer,
disabled && styles.disabledContainer,
]}>
{/* Search Icon */}
<Icon
name="search"
size={20}
color={isFocused ? theme.colors.primary : theme.colors.textMuted}
style={styles.searchIcon}
/>
{/* Text Input */}
<TextInput
style={[
styles.input,
disabled && styles.disabledInput,
]}
value={value}
onChangeText={handleTextChange}
onFocus={handleFocus}
onBlur={handleBlur}
placeholder={placeholder}
placeholderTextColor={theme.colors.textMuted}
autoFocus={autoFocus}
editable={!disabled}
selectTextOnFocus={!disabled}
autoCorrect={false}
autoCapitalize="none"
returnKeyType="search"
clearButtonMode="never" // We handle clear button manually
accessibilityLabel="Search AI predictions"
accessibilityHint="Enter patient ID, finding type, or location to search"
/>
{/* Clear Button */}
{value.length > 0 && !disabled && (
<TouchableOpacity
style={styles.clearButton}
onPress={handleClear}
accessibilityRole="button"
accessibilityLabel="Clear search"
accessibilityHint="Clear the search input"
>
<Icon
name="x"
size={18}
color={theme.colors.textMuted}
/>
</TouchableOpacity>
)}
</View>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.colors.background,
borderWidth: 1,
borderColor: theme.colors.border,
borderRadius: theme.borderRadius.medium,
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
marginHorizontal: theme.spacing.md,
marginVertical: theme.spacing.sm,
...theme.shadows.small,
},
focusedContainer: {
borderColor: theme.colors.primary,
backgroundColor: theme.colors.background,
},
disabledContainer: {
backgroundColor: theme.colors.backgroundAlt,
opacity: 0.6,
},
searchIcon: {
marginRight: theme.spacing.sm,
},
input: {
flex: 1,
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textPrimary,
paddingVertical: 0, // Remove default padding to maintain consistent height
fontFamily: theme.typography.fontFamily.regular,
},
disabledInput: {
color: theme.colors.textMuted,
},
clearButton: {
marginLeft: theme.spacing.sm,
padding: theme.spacing.xs,
},
});
export default SearchBar;
/*
* End of File: SearchBar.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,454 @@
/*
* File: StatsOverview.tsx
* Description: Statistics overview component for AI predictions dashboard
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Dimensions,
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme';
import type { AIPredictionStats } from '../types';
// ============================================================================
// INTERFACES
// ============================================================================
interface StatsOverviewProps {
stats: AIPredictionStats;
onStatsPress?: (statType: string) => void;
isLoading?: boolean;
style?: any;
}
interface StatCardProps {
title: string;
value: string | number;
subtitle?: string;
iconName: string;
color: string;
onPress?: () => void;
trend?: number;
isPercentage?: boolean;
}
// ============================================================================
// CONSTANTS
// ============================================================================
const { width } = Dimensions.get('window');
const CARD_WIDTH = (width - 48) / 2; // Two cards per row with margins
// ============================================================================
// STAT CARD COMPONENT
// ============================================================================
/**
* StatCard Component
*
* Purpose: Individual statistics card
*/
const StatCard: React.FC<StatCardProps> = ({
title,
value,
subtitle,
iconName,
color,
onPress,
trend,
isPercentage = false,
}) => {
const displayValue = typeof value === 'number'
? isPercentage
? `${Math.round(value * 100)}%`
: value.toLocaleString()
: value;
return (
<TouchableOpacity
style={[styles.statCard, { borderLeftColor: color }]}
onPress={onPress}
disabled={!onPress}
accessibilityRole="button"
accessibilityLabel={`${title}: ${displayValue}${subtitle ? `, ${subtitle}` : ''}`}
>
{/* Card Header */}
<View style={styles.cardHeader}>
<View style={{flexDirection: 'row', alignItems: 'center', gap: theme.spacing.sm}}>
<View style={[styles.iconContainer, { backgroundColor: color + '20' }]}>
<Icon name={iconName} size={20} color={color} />
</View>
<Text style={styles.statValue}>{displayValue}</Text>
</View>
{trend !== undefined && (
<View style={styles.trendContainer}>
<Icon
name={trend >= 0 ? 'trending-up' : 'trending-down'}
size={14}
color={trend >= 0 ? theme.colors.success : theme.colors.error}
/>
<Text style={[
styles.trendText,
{ color: trend >= 0 ? theme.colors.success : theme.colors.error }
]}>
{Math.abs(trend).toFixed(1)}%
</Text>
</View>
)}
</View>
{/* Card Content */}
<View style={styles.cardContent}>
<Text style={styles.statTitle} numberOfLines={2}>{title}</Text>
{subtitle && (
<Text style={styles.statSubtitle} numberOfLines={1}>{subtitle}</Text>
)}
</View>
</TouchableOpacity>
);
};
// ============================================================================
// STATS OVERVIEW COMPONENT
// ============================================================================
/**
* StatsOverview Component
*
* Purpose: Display comprehensive AI predictions statistics
*
* Features:
* - Total cases overview
* - Critical and urgent case counts
* - Review progress tracking
* - Average confidence metrics
* - Trend indicators
* - Interactive stat cards
* - Responsive grid layout
* - Modern card design
* - Accessibility support
*/
const StatsOverview: React.FC<StatsOverviewProps> = ({
stats,
onStatsPress,
isLoading = false,
style,
}) => {
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Handle Stat Press
*
* Purpose: Handle statistics card press
*/
const handleStatPress = (statType: string) => {
if (onStatsPress) {
onStatsPress(statType);
}
};
// ============================================================================
// RENDER
// ============================================================================
if (isLoading) {
return (
<View style={[styles.container, style]}>
<View style={styles.header}>
<Text style={styles.sectionTitle}>AI Predictions Overview</Text>
</View>
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>Loading statistics...</Text>
</View>
</View>
);
}
return (
<View style={[styles.container, style]}>
{/* Section Header */}
<View style={styles.header}>
<Text style={styles.sectionTitle}>AI Predictions Overview</Text>
<TouchableOpacity
style={styles.viewAllButton}
onPress={() => handleStatPress('all')}
accessibilityRole="button"
accessibilityLabel="View all statistics"
>
<Text style={styles.viewAllText}>View All</Text>
<Icon name="arrow-right" size={16} color={theme.colors.primary} />
</TouchableOpacity>
</View>
{/* Statistics Grid */}
<View style={styles.statsGrid}>
{/* Total Cases */}
<StatCard
title="Total Cases"
value={stats.totalCases}
subtitle="All predictions"
iconName="database"
color={theme.colors.primary}
onPress={() => handleStatPress('total')}
/>
{/* Critical Cases */}
<StatCard
title="Critical Cases"
value={stats.criticalCases}
subtitle="Require attention"
iconName="alert-triangle"
color={theme.colors.error}
onPress={() => handleStatPress('critical')}
/>
{/* Urgent Cases */}
<StatCard
title="Urgent Cases"
value={stats.urgentCases}
subtitle="High priority"
iconName="clock"
color={theme.colors.warning}
onPress={() => handleStatPress('urgent')}
/>
{/* Reviewed Cases */}
<StatCard
title="Reviewed Cases"
value={stats.reviewedCases}
subtitle="Completed reviews"
iconName="check-circle"
color={theme.colors.success}
onPress={() => handleStatPress('reviewed')}
/>
{/* Pending Cases */}
<StatCard
title="Pending Reviews"
value={stats.pendingCases}
subtitle="Awaiting review"
iconName="eye"
color={theme.colors.info}
onPress={() => handleStatPress('pending')}
/>
{/* Average Confidence */}
<StatCard
title="Avg Confidence"
value={stats.averageConfidence}
subtitle="AI accuracy"
iconName="trending-up"
color={theme.colors.primary}
onPress={() => handleStatPress('confidence')}
isPercentage={true}
/>
{/* Today's Cases */}
<StatCard
title="Today's Cases"
value={stats.todaysCases}
subtitle="New predictions"
iconName="calendar"
color={theme.colors.info}
onPress={() => handleStatPress('today')}
/>
{/* Weekly Trend */}
<StatCard
title="Weekly Trend"
value={`${stats.weeklyTrend >= 0 ? '+' : ''}${stats.weeklyTrend.toFixed(1)}%`}
subtitle="vs last week"
iconName={stats.weeklyTrend >= 0 ? 'trending-up' : 'trending-down'}
color={stats.weeklyTrend >= 0 ? theme.colors.success : theme.colors.error}
onPress={() => handleStatPress('trend')}
trend={stats.weeklyTrend}
/>
</View>
{/* Summary Section */}
<View style={styles.summarySection}>
<View style={styles.summaryCard}>
<View style={styles.summaryHeader}>
<Icon name="activity" size={20} color={theme.colors.primary} />
<Text style={styles.summaryTitle}>Quick Insights</Text>
</View>
<View style={styles.summaryContent}>
<View style={styles.summaryItem}>
<Text style={styles.summaryLabel}>Review Progress:</Text>
<Text style={styles.summaryValue}>
{Math.round((stats.reviewedCases / stats.totalCases) * 100)}%
</Text>
</View>
<View style={styles.summaryItem}>
<Text style={styles.summaryLabel}>Critical Rate:</Text>
<Text style={styles.summaryValue}>
{Math.round((stats.criticalCases / stats.totalCases) * 100)}%
</Text>
</View>
<View style={styles.summaryItem}>
<Text style={styles.summaryLabel}>Daily Average:</Text>
<Text style={styles.summaryValue}>
{Math.round(stats.totalCases / 7)} cases
</Text>
</View>
</View>
</View>
</View>
</View>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
backgroundColor: theme.colors.background,
paddingVertical: theme.spacing.lg,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
marginBottom: theme.spacing.lg,
},
sectionTitle: {
fontSize: theme.typography.fontSize.displaySmall,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
viewAllButton: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.xs,
},
viewAllText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.medium,
},
loadingContainer: {
paddingVertical: theme.spacing.xxl,
alignItems: 'center',
},
loadingText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textMuted,
},
statsGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
paddingHorizontal: theme.spacing.md,
gap: theme.spacing.md,
},
statCard: {
width: CARD_WIDTH,
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.medium,
borderLeftWidth: 4,
padding: theme.spacing.md,
...theme.shadows.medium,
},
cardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: theme.spacing.sm,
},
iconContainer: {
width: 36,
height: 36,
borderRadius: 18,
justifyContent: 'center',
alignItems: 'center',
},
trendContainer: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.xs,
},
trendText: {
fontSize: theme.typography.fontSize.caption,
fontWeight: theme.typography.fontWeight.medium,
},
cardContent: {
gap: theme.spacing.xs,
},
statValue: {
fontSize: theme.typography.fontSize.displayMedium,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
statTitle: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
fontWeight: theme.typography.fontWeight.medium,
},
statSubtitle: {
fontSize: theme.typography.fontSize.bodySmall,
color: theme.colors.textMuted,
},
summarySection: {
paddingHorizontal: theme.spacing.md,
marginTop: theme.spacing.lg,
},
summaryCard: {
backgroundColor: theme.colors.background,
borderRadius: theme.borderRadius.medium,
padding: theme.spacing.lg,
...theme.shadows.small,
},
summaryHeader: {
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing.sm,
marginBottom: theme.spacing.md,
},
summaryTitle: {
fontSize: theme.typography.fontSize.bodyLarge,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
summaryContent: {
gap: theme.spacing.sm,
},
summaryItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
summaryLabel: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
},
summaryValue: {
fontSize: theme.typography.fontSize.bodyMedium,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
});
export default StatsOverview;
/*
* End of File: StatsOverview.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,19 @@
/*
* File: index.ts
* Description: Components exports for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as AIPredictionCard } from './AIPredictionCard';
export { default as SearchBar } from './SearchBar';
export { default as FilterTabs } from './FilterTabs';
export { default as LoadingState } from './LoadingState';
export { default as EmptyState } from './EmptyState';
export { default as StatsOverview } from './StatsOverview';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,14 @@
/*
* File: index.ts
* Description: Hooks exports for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export * from './useAIPredictions';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,383 @@
/*
* File: useAIPredictions.ts
* Description: Custom hook for AI Predictions functionality
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { useCallback, useEffect, useMemo } from 'react';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
// Import Redux actions and selectors
import {
fetchAIPredictions,
setSearchQuery,
setUrgencyFilter,
setSeverityFilter,
setCategoryFilter,
clearAllFilters,
updateCaseReview,
} from '../redux';
import {
selectPaginatedCases,
selectIsLoading,
selectError,
selectSearchQuery,
selectUrgencyFilter,
selectSeverityFilter,
selectCategoryFilter,
selectCasesStatistics,
selectActiveFiltersCount,
selectCurrentPage,
selectTotalPages,
} from '../redux';
// Import auth selector
import { selectUser } from '../../Auth/redux/authSelectors';
// Import types
import type { AIPredictionState } from '../types';
// ============================================================================
// INTERFACES
// ============================================================================
interface UseAIPredictionsOptions {
autoLoad?: boolean;
refreshInterval?: number;
}
interface UseAIPredictionsReturn {
// Data
cases: ReturnType<typeof selectPaginatedCases>;
statistics: ReturnType<typeof selectCasesStatistics>;
// Loading states
isLoading: boolean;
error: string | null;
// Filters
searchQuery: string;
urgencyFilter: AIPredictionState['selectedUrgencyFilter'];
severityFilter: AIPredictionState['selectedSeverityFilter'];
categoryFilter: AIPredictionState['selectedCategoryFilter'];
activeFiltersCount: number;
// Pagination
currentPage: number;
totalPages: number;
// Actions
loadPredictions: () => Promise<void>;
refreshPredictions: () => Promise<void>;
setSearch: (query: string) => void;
setUrgency: (filter: AIPredictionState['selectedUrgencyFilter']) => void;
setSeverity: (filter: AIPredictionState['selectedSeverityFilter']) => void;
setCategory: (filter: AIPredictionState['selectedCategoryFilter']) => void;
clearFilters: () => void;
reviewCase: (caseId: string, reviewData?: Partial<{
review_status: 'pending' | 'reviewed' | 'confirmed' | 'disputed';
reviewed_by: string;
review_notes: string;
priority: 'critical' | 'high' | 'medium' | 'low';
}>) => Promise<void>;
// Computed properties
hasFilters: boolean;
isEmpty: boolean;
hasError: boolean;
}
// ============================================================================
// USE AI PREDICTIONS HOOK
// ============================================================================
/**
* useAIPredictions Hook
*
* Purpose: Custom hook for managing AI predictions state and actions
*
* Features:
* - Automatic data loading on mount
* - Search and filtering functionality
* - Case review management
* - Error handling
* - Loading states
* - Computed properties for UI state
* - Auto-refresh capability
* - Type-safe actions and selectors
*/
export const useAIPredictions = (options: UseAIPredictionsOptions = {}): UseAIPredictionsReturn => {
const {
autoLoad = true,
refreshInterval,
} = options;
// ============================================================================
// REDUX STATE
// ============================================================================
const dispatch = useAppDispatch();
// Auth state
const user = useAppSelector(selectUser);
// AI Predictions state
const cases = useAppSelector(selectPaginatedCases);
const statistics = useAppSelector(selectCasesStatistics);
const isLoading = useAppSelector(selectIsLoading);
const error = useAppSelector(selectError);
const searchQuery = useAppSelector(selectSearchQuery);
const urgencyFilter = useAppSelector(selectUrgencyFilter);
const severityFilter = useAppSelector(selectSeverityFilter);
const categoryFilter = useAppSelector(selectCategoryFilter);
const activeFiltersCount = useAppSelector(selectActiveFiltersCount);
const currentPage = useAppSelector(selectCurrentPage);
const totalPages = useAppSelector(selectTotalPages);
// ============================================================================
// MEMOIZED VALUES
// ============================================================================
/**
* Has Filters
*
* Purpose: Check if any filters are active
*/
const hasFilters = useMemo(() => activeFiltersCount > 0, [activeFiltersCount]);
/**
* Is Empty
*
* Purpose: Check if the cases list is empty
*/
const isEmpty = useMemo(() => cases.length === 0, [cases.length]);
/**
* Has Error
*
* Purpose: Check if there's an error
*/
const hasError = useMemo(() => error !== null, [error]);
// ============================================================================
// ACTIONS
// ============================================================================
/**
* Load Predictions
*
* Purpose: Load AI predictions from API
*/
const loadPredictions = useCallback(async () => {
if (!user?.access_token) {
throw new Error('User not authenticated');
}
try {
const params = {
page: currentPage,
limit: 20,
...(urgencyFilter !== 'all' && { urgency: urgencyFilter }),
...(severityFilter !== 'all' && { severity: severityFilter }),
...(categoryFilter !== 'all' && { category: categoryFilter }),
...(searchQuery.trim() && { search: searchQuery.trim() }),
};
await dispatch(fetchAIPredictions({
token: user.access_token,
params,
})).unwrap();
} catch (error) {
console.error('Failed to load AI predictions:', error);
throw error;
}
}, [
dispatch,
user?.access_token,
currentPage,
urgencyFilter,
severityFilter,
categoryFilter,
searchQuery,
]);
/**
* Refresh Predictions
*
* Purpose: Refresh AI predictions data
*/
const refreshPredictions = useCallback(async () => {
await loadPredictions();
}, [loadPredictions]);
/**
* Set Search
*
* Purpose: Set search query
*/
const setSearch = useCallback((query: string) => {
dispatch(setSearchQuery(query));
}, [dispatch]);
/**
* Set Urgency Filter
*
* Purpose: Set urgency filter
*/
const setUrgency = useCallback((filter: AIPredictionState['selectedUrgencyFilter']) => {
dispatch(setUrgencyFilter(filter));
}, [dispatch]);
/**
* Set Severity Filter
*
* Purpose: Set severity filter
*/
const setSeverity = useCallback((filter: AIPredictionState['selectedSeverityFilter']) => {
dispatch(setSeverityFilter(filter));
}, [dispatch]);
/**
* Set Category Filter
*
* Purpose: Set category filter
*/
const setCategory = useCallback((filter: AIPredictionState['selectedCategoryFilter']) => {
dispatch(setCategoryFilter(filter));
}, [dispatch]);
/**
* Clear Filters
*
* Purpose: Clear all active filters
*/
const clearFilters = useCallback(() => {
dispatch(clearAllFilters());
}, [dispatch]);
/**
* Review Case
*
* Purpose: Update case review status
*/
const reviewCase = useCallback(async (
caseId: string,
reviewData: Partial<{
review_status: 'pending' | 'reviewed' | 'confirmed' | 'disputed';
reviewed_by: string;
review_notes: string;
priority: 'critical' | 'high' | 'medium' | 'low';
}> = {}
) => {
if (!user?.access_token) {
throw new Error('User not authenticated');
}
try {
const defaultReviewData = {
review_status: 'reviewed' as const,
reviewed_by: user.display_name || user.email || 'Current User',
...reviewData,
};
await dispatch(updateCaseReview({
caseId,
reviewData: defaultReviewData,
token: user.access_token,
})).unwrap();
} catch (error) {
console.error('Failed to review case:', error);
throw error;
}
}, [dispatch, user]);
// ============================================================================
// EFFECTS
// ============================================================================
/**
* Auto-load Effect
*
* Purpose: Automatically load predictions on mount if enabled
*/
useEffect(() => {
if (autoLoad && user?.access_token) {
loadPredictions().catch(console.error);
}
}, [autoLoad, user?.access_token, loadPredictions]);
/**
* Auto-refresh Effect
*
* Purpose: Set up auto-refresh interval if specified
*/
useEffect(() => {
if (!refreshInterval || !user?.access_token) return;
const interval = setInterval(() => {
loadPredictions().catch(console.error);
}, refreshInterval);
return () => clearInterval(interval);
}, [refreshInterval, user?.access_token, loadPredictions]);
/**
* Filter Change Effect
*
* Purpose: Reload data when filters change
*/
useEffect(() => {
if (user?.access_token) {
loadPredictions().catch(console.error);
}
}, [urgencyFilter, severityFilter, categoryFilter, searchQuery, currentPage]);
// ============================================================================
// RETURN
// ============================================================================
return {
// Data
cases,
statistics,
// Loading states
isLoading,
error,
// Filters
searchQuery,
urgencyFilter,
severityFilter,
categoryFilter,
activeFiltersCount,
// Pagination
currentPage,
totalPages,
// Actions
loadPredictions,
refreshPredictions,
setSearch,
setUrgency,
setSeverity,
setCategory,
clearFilters,
reviewCase,
// Computed properties
hasFilters,
isEmpty,
hasError,
};
};
/*
* End of File: useAIPredictions.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,54 @@
/*
* File: index.ts
* Description: Main exports for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
// ============================================================================
// COMPONENT EXPORTS
// ============================================================================
export * from './components';
// ============================================================================
// SCREEN EXPORTS
// ============================================================================
export * from './screens';
// ============================================================================
// NAVIGATION EXPORTS
// ============================================================================
export * from './navigation';
// ============================================================================
// REDUX EXPORTS
// ============================================================================
export * from './redux';
// ============================================================================
// SERVICE EXPORTS
// ============================================================================
export * from './services';
// ============================================================================
// TYPE EXPORTS
// ============================================================================
export * from './types';
// ============================================================================
// HOOK EXPORTS
// ============================================================================
export * from './hooks';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,244 @@
/*
* File: AIPredictionStackNavigator.tsx
* Description: Stack navigator for AI Prediction module screens
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme';
// Import screens
import { AIPredictionsScreen } from '../screens';
import { ComingSoonScreen } from '../../../shared/components';
// Import types
import type { AIPredictionStackParamList } from './navigationTypes';
// ============================================================================
// STACK NAVIGATOR SETUP
// ============================================================================
const Stack = createStackNavigator<AIPredictionStackParamList>();
// ============================================================================
// HEADER COMPONENTS
// ============================================================================
/**
* Header Back Button
*
* Purpose: Custom back button for navigation header
*/
const HeaderBackButton: React.FC<{ onPress: () => void }> = ({ onPress }) => (
<TouchableOpacity style={styles.headerButton} onPress={onPress}>
<Icon name="arrow-left" size={24} color={theme.colors.textPrimary} />
</TouchableOpacity>
);
/**
* Header Action Button
*
* Purpose: Custom action button for navigation header
*/
const HeaderActionButton: React.FC<{
iconName: string;
onPress: () => void;
accessibilityLabel?: string;
}> = ({ iconName, onPress, accessibilityLabel }) => (
<TouchableOpacity
style={styles.headerButton}
onPress={onPress}
accessibilityRole="button"
accessibilityLabel={accessibilityLabel}
>
<Icon name={iconName} size={24} color={theme.colors.textPrimary} />
</TouchableOpacity>
);
// ============================================================================
// SCREEN OPTIONS
// ============================================================================
/**
* Default Screen Options
*
* Purpose: Common screen options for all AI prediction screens
*/
const defaultScreenOptions = {
headerStyle: {
backgroundColor: theme.colors.background,
elevation: 2,
shadowOpacity: 0.1,
shadowRadius: 4,
shadowOffset: { width: 0, height: 2 },
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
},
headerTitleStyle: {
fontSize: theme.typography.fontSize.bodyLarge,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
headerTintColor: theme.colors.textPrimary,
headerBackTitleVisible: false,
gestureEnabled: true,
cardStyleInterpolator: ({ current, layouts }: any) => {
return {
cardStyle: {
transform: [
{
translateX: current.progress.interpolate({
inputRange: [0, 1],
outputRange: [layouts.screen.width, 0],
}),
},
],
},
};
},
};
// ============================================================================
// AI PREDICTION STACK NAVIGATOR COMPONENT
// ============================================================================
/**
* AIPredictionStackNavigator Component
*
* Purpose: Stack navigator for AI prediction module
*
* Features:
* - AI Prediction List screen (main screen)
* - AI Prediction Details screen (case details)
* - AI Prediction Filters screen (advanced filtering)
* - AI Prediction Stats screen (detailed statistics)
* - Custom header styling and buttons
* - Smooth navigation transitions
* - Accessibility support
* - Coming soon screens for unimplemented features
*/
const AIPredictionStackNavigator: React.FC = () => {
return (
<Stack.Navigator
initialRouteName="AIPredictionList"
screenOptions={defaultScreenOptions}
>
{/* AI Prediction List Screen */}
<Stack.Screen
name="AIPredictionList"
component={AIPredictionsScreen}
options={({ navigation }) => ({
title: 'AI Predictions',
headerLeft: () => null, // No back button on main screen
headerRight: () => (
<HeaderActionButton
iconName="more-vertical"
onPress={() => {
// Open options menu
// For now, just navigate to stats
// @ts-ignore
navigation.navigate('AIPredictionStats');
}}
accessibilityLabel="More options"
/>
),
})}
/>
{/* AI Prediction Details Screen */}
<Stack.Screen
name="AIPredictionDetails"
component={ComingSoonScreen}
options={({ navigation, route }) => ({
title: 'Prediction Details',
headerLeft: () => (
<HeaderBackButton onPress={() => navigation.goBack()} />
),
headerRight: () => (
<HeaderActionButton
iconName="share-2"
onPress={() => {
// Share prediction details
console.log('Share prediction:', route.params?.caseId);
}}
accessibilityLabel="Share prediction"
/>
),
})}
/>
{/* AI Prediction Filters Screen */}
<Stack.Screen
name="AIPredictionFilters"
component={ComingSoonScreen}
options={({ navigation }) => ({
title: 'Advanced Filters',
headerLeft: () => (
<HeaderBackButton onPress={() => navigation.goBack()} />
),
headerRight: () => (
<HeaderActionButton
iconName="refresh-cw"
onPress={() => {
// Reset filters
console.log('Reset filters');
}}
accessibilityLabel="Reset filters"
/>
),
})}
/>
{/* AI Prediction Stats Screen */}
<Stack.Screen
name="AIPredictionStats"
component={ComingSoonScreen}
options={({ navigation, route }) => ({
title: 'Statistics',
headerLeft: () => (
<HeaderBackButton onPress={() => navigation.goBack()} />
),
headerRight: () => (
<HeaderActionButton
iconName="download"
onPress={() => {
// Export statistics
console.log('Export stats:', route.params?.timeRange);
}}
accessibilityLabel="Export statistics"
/>
),
})}
/>
</Stack.Navigator>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
headerButton: {
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
marginHorizontal: theme.spacing.xs,
},
headerButtonText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.medium,
},
});
export default AIPredictionStackNavigator;
/*
* End of File: AIPredictionStackNavigator.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,16 @@
/*
* File: index.ts
* Description: Navigation exports for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as AIPredictionStackNavigator } from './AIPredictionStackNavigator';
export * from './navigationTypes';
export * from './navigationUtils';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,169 @@
/*
* File: navigationTypes.ts
* Description: Navigation type definitions for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import type { StackNavigationProp } from '@react-navigation/stack';
import type { RouteProp } from '@react-navigation/native';
// ============================================================================
// AI PREDICTION STACK PARAM LIST
// ============================================================================
/**
* AI Prediction Stack Param List
*
* Purpose: Define navigation parameters for AI prediction screens
*
* Screens:
* - AIPredictionList: Main list of AI predictions
* - AIPredictionDetails: Detailed view of a specific prediction
* - AIPredictionFilters: Advanced filtering options
* - AIPredictionStats: Detailed statistics view
*/
export type AIPredictionStackParamList = {
AIPredictionList: undefined;
AIPredictionDetails: { caseId: string };
AIPredictionFilters: undefined;
AIPredictionStats: { timeRange?: 'today' | 'week' | 'month' };
};
// ============================================================================
// NAVIGATION PROP TYPES
// ============================================================================
/**
* AI Prediction List Navigation Prop
*
* Purpose: Navigation prop type for AI prediction list screen
*/
export type AIPredictionListNavigationProp = StackNavigationProp<
AIPredictionStackParamList,
'AIPredictionList'
>;
/**
* AI Prediction Details Navigation Prop
*
* Purpose: Navigation prop type for AI prediction details screen
*/
export type AIPredictionDetailsNavigationProp = StackNavigationProp<
AIPredictionStackParamList,
'AIPredictionDetails'
>;
/**
* AI Prediction Filters Navigation Prop
*
* Purpose: Navigation prop type for AI prediction filters screen
*/
export type AIPredictionFiltersNavigationProp = StackNavigationProp<
AIPredictionStackParamList,
'AIPredictionFilters'
>;
/**
* AI Prediction Stats Navigation Prop
*
* Purpose: Navigation prop type for AI prediction statistics screen
*/
export type AIPredictionStatsNavigationProp = StackNavigationProp<
AIPredictionStackParamList,
'AIPredictionStats'
>;
// ============================================================================
// ROUTE PROP TYPES
// ============================================================================
/**
* AI Prediction List Route Prop
*
* Purpose: Route prop type for AI prediction list screen
*/
export type AIPredictionListRouteProp = RouteProp<
AIPredictionStackParamList,
'AIPredictionList'
>;
/**
* AI Prediction Details Route Prop
*
* Purpose: Route prop type for AI prediction details screen
*/
export type AIPredictionDetailsRouteProp = RouteProp<
AIPredictionStackParamList,
'AIPredictionDetails'
>;
/**
* AI Prediction Filters Route Prop
*
* Purpose: Route prop type for AI prediction filters screen
*/
export type AIPredictionFiltersRouteProp = RouteProp<
AIPredictionStackParamList,
'AIPredictionFilters'
>;
/**
* AI Prediction Stats Route Prop
*
* Purpose: Route prop type for AI prediction statistics screen
*/
export type AIPredictionStatsRouteProp = RouteProp<
AIPredictionStackParamList,
'AIPredictionStats'
>;
// ============================================================================
// COMBINED PROP TYPES
// ============================================================================
/**
* AI Prediction List Screen Props
*
* Purpose: Combined props for AI prediction list screen
*/
export interface AIPredictionListScreenProps {
navigation: AIPredictionListNavigationProp;
route: AIPredictionListRouteProp;
}
/**
* AI Prediction Details Screen Props
*
* Purpose: Combined props for AI prediction details screen
*/
export interface AIPredictionDetailsScreenProps {
navigation: AIPredictionDetailsNavigationProp;
route: AIPredictionDetailsRouteProp;
}
/**
* AI Prediction Filters Screen Props
*
* Purpose: Combined props for AI prediction filters screen
*/
export interface AIPredictionFiltersScreenProps {
navigation: AIPredictionFiltersNavigationProp;
route: AIPredictionFiltersRouteProp;
}
/**
* AI Prediction Stats Screen Props
*
* Purpose: Combined props for AI prediction statistics screen
*/
export interface AIPredictionStatsScreenProps {
navigation: AIPredictionStatsNavigationProp;
route: AIPredictionStatsRouteProp;
}
/*
* End of File: navigationTypes.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,251 @@
/*
* File: navigationUtils.ts
* Description: Navigation utility functions for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { CommonActions } from '@react-navigation/native';
import type { AIPredictionStackParamList } from './navigationTypes';
// ============================================================================
// NAVIGATION UTILITY FUNCTIONS
// ============================================================================
/**
* Navigate to AI Prediction Details
*
* Purpose: Navigate to AI prediction case details screen
*
* @param navigation - Navigation object
* @param caseId - AI prediction case ID
*/
export const navigateToAIPredictionDetails = (
navigation: any,
caseId: string
) => {
navigation.navigate('AIPredictionDetails', { caseId });
};
/**
* Navigate to AI Prediction Filters
*
* Purpose: Navigate to advanced filters screen
*
* @param navigation - Navigation object
*/
export const navigateToAIPredictionFilters = (navigation: any) => {
navigation.navigate('AIPredictionFilters');
};
/**
* Navigate to AI Prediction Statistics
*
* Purpose: Navigate to detailed statistics screen
*
* @param navigation - Navigation object
* @param timeRange - Optional time range filter
*/
export const navigateToAIPredictionStats = (
navigation: any,
timeRange?: 'today' | 'week' | 'month'
) => {
navigation.navigate('AIPredictionStats', { timeRange });
};
/**
* Go Back to AI Prediction List
*
* Purpose: Navigate back to AI prediction list screen
*
* @param navigation - Navigation object
*/
export const goBackToAIPredictionList = (navigation: any) => {
navigation.navigate('AIPredictionList');
};
/**
* Reset to AI Prediction List
*
* Purpose: Reset navigation stack to AI prediction list
*
* @param navigation - Navigation object
*/
export const resetToAIPredictionList = (navigation: any) => {
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: 'AIPredictionList' }],
})
);
};
/**
* Can Go Back
*
* Purpose: Check if navigation can go back
*
* @param navigation - Navigation object
* @returns Boolean indicating if can go back
*/
export const canGoBack = (navigation: any): boolean => {
return navigation.canGoBack();
};
/**
* Get Current Route Name
*
* Purpose: Get the current route name
*
* @param navigation - Navigation object
* @returns Current route name or undefined
*/
export const getCurrentRouteName = (navigation: any): string | undefined => {
return navigation.getCurrentRoute()?.name;
};
/**
* Get Current Route Params
*
* Purpose: Get the current route parameters
*
* @param navigation - Navigation object
* @returns Current route params or undefined
*/
export const getCurrentRouteParams = (navigation: any): any => {
return navigation.getCurrentRoute()?.params;
};
/**
* Navigate with Replace
*
* Purpose: Navigate to a screen by replacing the current one
*
* @param navigation - Navigation object
* @param routeName - Route name to navigate to
* @param params - Optional route parameters
*/
export const navigateWithReplace = (
navigation: any,
routeName: keyof AIPredictionStackParamList,
params?: any
) => {
navigation.replace(routeName, params);
};
/**
* Navigate with Push
*
* Purpose: Navigate to a screen by pushing it onto the stack
*
* @param navigation - Navigation object
* @param routeName - Route name to navigate to
* @param params - Optional route parameters
*/
export const navigateWithPush = (
navigation: any,
routeName: keyof AIPredictionStackParamList,
params?: any
) => {
navigation.push(routeName, params);
};
/**
* Pop Navigation Stack
*
* Purpose: Pop the specified number of screens from the stack
*
* @param navigation - Navigation object
* @param count - Number of screens to pop (default: 1)
*/
export const popNavigationStack = (navigation: any, count: number = 1) => {
navigation.pop(count);
};
/**
* Pop to Top
*
* Purpose: Pop to the top of the navigation stack
*
* @param navigation - Navigation object
*/
export const popToTop = (navigation: any) => {
navigation.popToTop();
};
/**
* Set Navigation Params
*
* Purpose: Set parameters for the current screen
*
* @param navigation - Navigation object
* @param params - Parameters to set
*/
export const setNavigationParams = (navigation: any, params: any) => {
navigation.setParams(params);
};
/**
* Add Navigation Listener
*
* Purpose: Add a navigation event listener
*
* @param navigation - Navigation object
* @param eventName - Event name to listen for
* @param callback - Callback function
* @returns Unsubscribe function
*/
export const addNavigationListener = (
navigation: any,
eventName: string,
callback: (e: any) => void
) => {
return navigation.addListener(eventName, callback);
};
/**
* Remove Navigation Listener
*
* Purpose: Remove a navigation event listener
*
* @param navigation - Navigation object
* @param eventName - Event name
* @param callback - Callback function
*/
export const removeNavigationListener = (
navigation: any,
eventName: string,
callback: (e: any) => void
) => {
navigation.removeListener(eventName, callback);
};
/**
* Check if Screen is Focused
*
* Purpose: Check if the current screen is focused
*
* @param navigation - Navigation object
* @returns Boolean indicating if screen is focused
*/
export const isScreenFocused = (navigation: any): boolean => {
return navigation.isFocused();
};
/**
* Get Navigation State
*
* Purpose: Get the current navigation state
*
* @param navigation - Navigation object
* @returns Navigation state
*/
export const getNavigationState = (navigation: any) => {
return navigation.getState();
};
/*
* End of File: navigationUtils.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,410 @@
/*
* File: aiPredictionSelectors.ts
* Description: Redux selectors for AI Prediction state
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from '../../../store';
import { AIPredictionCase } from '../types';
// ============================================================================
// BASE SELECTORS
// ============================================================================
/**
* Select AI Prediction State
*
* Purpose: Get the entire AI prediction state
*/
export const selectAIPredictionState = (state: RootState) => state.aiPrediction;
/**
* Select Prediction Cases
*
* Purpose: Get all AI prediction cases
*/
export const selectPredictionCases = (state: RootState) => state.aiPrediction.predictionCases;
/**
* Select Current Case
*
* Purpose: Get the currently selected AI prediction case
*/
export const selectCurrentCase = (state: RootState) => state.aiPrediction.currentCase;
/**
* Select Loading State
*
* Purpose: Get the loading state for AI predictions
*/
export const selectIsLoading = (state: RootState) => state.aiPrediction.isLoading;
/**
* Select Loading Case Details State
*
* Purpose: Get the loading state for case details
*/
export const selectIsLoadingCaseDetails = (state: RootState) => state.aiPrediction.isLoadingCaseDetails;
/**
* Select Error
*
* Purpose: Get the current error message
*/
export const selectError = (state: RootState) => state.aiPrediction.error;
/**
* Select Search Query
*
* Purpose: Get the current search query
*/
export const selectSearchQuery = (state: RootState) => state.aiPrediction.searchQuery;
/**
* Select Filter States
*
* Purpose: Get all filter states
*/
export const selectUrgencyFilter = (state: RootState) => state.aiPrediction.selectedUrgencyFilter;
export const selectSeverityFilter = (state: RootState) => state.aiPrediction.selectedSeverityFilter;
export const selectCategoryFilter = (state: RootState) => state.aiPrediction.selectedCategoryFilter;
/**
* Select Sort Options
*
* Purpose: Get current sort configuration
*/
export const selectSortBy = (state: RootState) => state.aiPrediction.sortBy;
export const selectSortOrder = (state: RootState) => state.aiPrediction.sortOrder;
/**
* Select Pagination
*
* Purpose: Get pagination configuration
*/
export const selectCurrentPage = (state: RootState) => state.aiPrediction.currentPage;
export const selectItemsPerPage = (state: RootState) => state.aiPrediction.itemsPerPage;
export const selectTotalItems = (state: RootState) => state.aiPrediction.totalItems;
/**
* Select UI State
*
* Purpose: Get UI state flags
*/
export const selectShowFilters = (state: RootState) => state.aiPrediction.showFilters;
export const selectSelectedCaseIds = (state: RootState) => state.aiPrediction.selectedCaseIds;
// ============================================================================
// COMPUTED SELECTORS
// ============================================================================
/**
* Select Filtered and Sorted Cases
*
* Purpose: Get AI prediction cases filtered and sorted based on current settings
*/
export const selectFilteredAndSortedCases = createSelector(
[
selectPredictionCases,
selectSearchQuery,
selectUrgencyFilter,
selectSeverityFilter,
selectCategoryFilter,
selectSortBy,
selectSortOrder,
],
(cases, searchQuery, urgencyFilter, severityFilter, categoryFilter, sortBy, sortOrder) => {
let filteredCases = [...cases];
// Apply search filter
if (searchQuery.trim()) {
const query = searchQuery.toLowerCase();
filteredCases = filteredCases.filter(case_ =>
case_.patid.toLowerCase().includes(query) ||
case_.prediction.label.toLowerCase().includes(query) ||
case_.prediction.anatomical_location.toLowerCase().includes(query)
);
}
// Apply urgency filter
if (urgencyFilter !== 'all') {
filteredCases = filteredCases.filter(case_ =>
case_.prediction.clinical_urgency === urgencyFilter
);
}
// Apply severity filter
if (severityFilter !== 'all') {
filteredCases = filteredCases.filter(case_ =>
case_.prediction.primary_severity === severityFilter
);
}
// Apply category filter
if (categoryFilter !== 'all') {
filteredCases = filteredCases.filter(case_ =>
case_.prediction.finding_category === categoryFilter
);
}
// Apply sorting
filteredCases.sort((a, b) => {
let comparison = 0;
switch (sortBy) {
case 'date':
comparison = new Date(a.created_at || '').getTime() - new Date(b.created_at || '').getTime();
break;
case 'urgency':
const urgencyOrder = { emergency: 5, urgent: 4, moderate: 3, low: 2, routine: 1 };
comparison = (urgencyOrder[a.prediction.clinical_urgency as keyof typeof urgencyOrder] || 0) -
(urgencyOrder[b.prediction.clinical_urgency as keyof typeof urgencyOrder] || 0);
break;
case 'confidence':
comparison = a.prediction.confidence_score - b.prediction.confidence_score;
break;
case 'severity':
const severityOrder = { high: 4, medium: 3, low: 2, none: 1 };
comparison = (severityOrder[a.prediction.primary_severity as keyof typeof severityOrder] || 0) -
(severityOrder[b.prediction.primary_severity as keyof typeof severityOrder] || 0);
break;
default:
break;
}
return sortOrder === 'desc' ? -comparison : comparison;
});
return filteredCases;
}
);
/**
* Select Paginated Cases
*
* Purpose: Get the current page of filtered and sorted cases
*/
export const selectPaginatedCases = createSelector(
[selectFilteredAndSortedCases, selectCurrentPage, selectItemsPerPage],
(filteredCases, currentPage, itemsPerPage) => {
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
return filteredCases.slice(startIndex, endIndex);
}
);
/**
* Select Critical Cases
*
* Purpose: Get cases marked as critical or emergency
*/
export const selectCriticalCases = createSelector(
[selectPredictionCases],
(cases) => cases.filter(case_ =>
case_.prediction.clinical_urgency === 'emergency' ||
case_.prediction.clinical_urgency === 'urgent' ||
case_.prediction.primary_severity === 'high' ||
case_.priority === 'critical'
)
);
/**
* Select Pending Cases
*
* Purpose: Get cases pending review
*/
export const selectPendingCases = createSelector(
[selectPredictionCases],
(cases) => cases.filter(case_ => case_.review_status === 'pending')
);
/**
* Select Reviewed Cases
*
* Purpose: Get cases that have been reviewed
*/
export const selectReviewedCases = createSelector(
[selectPredictionCases],
(cases) => cases.filter(case_ =>
case_.review_status === 'reviewed' ||
case_.review_status === 'confirmed' ||
case_.review_status === 'disputed'
)
);
/**
* Select Cases by Urgency
*
* Purpose: Group cases by urgency level
*/
export const selectCasesByUrgency = createSelector(
[selectPredictionCases],
(cases) => {
const grouped = {
emergency: [] as AIPredictionCase[],
urgent: [] as AIPredictionCase[],
moderate: [] as AIPredictionCase[],
low: [] as AIPredictionCase[],
routine: [] as AIPredictionCase[],
};
cases.forEach(case_ => {
const urgency = case_.prediction.clinical_urgency as keyof typeof grouped;
if (grouped[urgency]) {
grouped[urgency].push(case_);
}
});
return grouped;
}
);
/**
* Select Cases Statistics
*
* Purpose: Get statistical overview of cases
*/
export const selectCasesStatistics = createSelector(
[selectPredictionCases],
(cases) => {
const total = cases.length;
const critical = cases.filter(c =>
c.prediction.clinical_urgency === 'emergency' ||
c.prediction.clinical_urgency === 'urgent'
).length;
const pending = cases.filter(c => c.review_status === 'pending').length;
const reviewed = cases.filter(c =>
c.review_status === 'reviewed' ||
c.review_status === 'confirmed'
).length;
const averageConfidence = total > 0
? cases.reduce((sum, c) => sum + c.prediction.confidence_score, 0) / total
: 0;
return {
total,
critical,
pending,
reviewed,
averageConfidence: Math.round(averageConfidence * 1000) / 1000, // Round to 3 decimal places
reviewProgress: total > 0 ? Math.round((reviewed / total) * 100) : 0,
};
}
);
/**
* Select Filter Counts
*
* Purpose: Get counts for each filter option
*/
export const selectFilterCounts = createSelector(
[selectPredictionCases],
(cases) => {
const urgencyCounts = {
all: cases.length,
emergency: 0,
urgent: 0,
moderate: 0,
low: 0,
routine: 0,
};
const severityCounts = {
all: cases.length,
high: 0,
medium: 0,
low: 0,
none: 0,
};
const categoryCounts = {
all: cases.length,
normal: 0,
abnormal: 0,
critical: 0,
warning: 0,
unknown: 0,
};
cases.forEach(case_ => {
// Count urgency
const urgency = case_.prediction.clinical_urgency as keyof typeof urgencyCounts;
if (urgencyCounts[urgency] !== undefined) {
urgencyCounts[urgency]++;
}
// Count severity
const severity = case_.prediction.primary_severity as keyof typeof severityCounts;
if (severityCounts[severity] !== undefined) {
severityCounts[severity]++;
}
// Count category
const category = case_.prediction.finding_category as keyof typeof categoryCounts;
if (categoryCounts[category] !== undefined) {
categoryCounts[category]++;
}
});
return {
urgency: urgencyCounts,
severity: severityCounts,
category: categoryCounts,
};
}
);
/**
* Select Total Pages
*
* Purpose: Calculate total number of pages based on filtered results
*/
export const selectTotalPages = createSelector(
[selectFilteredAndSortedCases, selectItemsPerPage],
(filteredCases, itemsPerPage) => Math.ceil(filteredCases.length / itemsPerPage)
);
/**
* Select Has Previous Page
*
* Purpose: Check if there's a previous page available
*/
export const selectHasPreviousPage = createSelector(
[selectCurrentPage],
(currentPage) => currentPage > 1
);
/**
* Select Has Next Page
*
* Purpose: Check if there's a next page available
*/
export const selectHasNextPage = createSelector(
[selectCurrentPage, selectTotalPages],
(currentPage, totalPages) => currentPage < totalPages
);
/**
* Select Active Filters Count
*
* Purpose: Count how many filters are currently active
*/
export const selectActiveFiltersCount = createSelector(
[selectSearchQuery, selectUrgencyFilter, selectSeverityFilter, selectCategoryFilter],
(searchQuery, urgencyFilter, severityFilter, categoryFilter) => {
let count = 0;
if (searchQuery.trim()) count++;
if (urgencyFilter !== 'all') count++;
if (severityFilter !== 'all') count++;
if (categoryFilter !== 'all') count++;
return count;
}
);
/*
* End of File: aiPredictionSelectors.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,621 @@
/*
* File: aiPredictionSlice.ts
* Description: Redux slice for AI Prediction state management
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import {
AIPredictionCase,
AIPredictionState,
AIPredictionStats,
AIPredictionAPIResponse
} from '../types';
import { aiPredictionAPI } from '../services';
// ============================================================================
// ASYNC THUNKS
// ============================================================================
/**
* Fetch AI Predictions Async Thunk
*
* Purpose: Fetch AI prediction results from API
*
* @param token - Authentication token
* @param params - Optional query parameters for filtering
* @returns Promise with AI prediction data or error
*/
export const fetchAIPredictions = createAsyncThunk(
'aiPrediction/fetchAIPredictions',
async (payload: {
token: string;
params?: {
page?: number;
limit?: number;
urgency?: string;
severity?: string;
category?: string;
search?: string;
}
}, { rejectWithValue }) => {
try {
const response: any = await aiPredictionAPI.getAllPredictions(payload.token, payload.params);
console.log('AI predictions response:', response);
if (response.ok && response.data && response.data.success) {
// Add additional metadata to each case for UI purposes
const enhancedCases = response.data.data.map((aiCase: AIPredictionCase) => ({
...aiCase,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
review_status: 'pending' as const,
priority: getPriorityFromPrediction(aiCase.prediction)
}));
console.log('Enhanced AI prediction cases:', enhancedCases);
return {
cases: enhancedCases as AIPredictionCase[],
total: response.data.total || enhancedCases.length,
page: response.data.page || 1,
limit: response.data.limit || 20
};
} else {
// Fallback to mock data for development
const mockData = generateMockAIPredictions();
return {
cases: mockData,
total: mockData.length,
page: 1,
limit: 20
};
}
} catch (error: any) {
console.error('Fetch AI predictions error:', error);
return rejectWithValue(error.message || 'Failed to fetch AI predictions.');
}
}
);
/**
* Fetch AI Prediction Case Details Async Thunk
*
* Purpose: Fetch detailed information for a specific AI prediction case
*
* @param caseId - AI prediction case ID
* @param token - Authentication token
* @returns Promise with case details or error
*/
export const fetchAIPredictionDetails = createAsyncThunk(
'aiPrediction/fetchAIPredictionDetails',
async (payload: { caseId: string; token: string }, { rejectWithValue }) => {
try {
const response: any = await aiPredictionAPI.getCaseDetails(payload.caseId, payload.token);
if (response.ok && response.data) {
return response.data as AIPredictionCase;
} else {
// Fallback to mock data
const mockCase = generateMockAIPredictions().find(c => c.patid === payload.caseId);
if (mockCase) {
return mockCase;
}
throw new Error('Case not found');
}
} catch (error: any) {
console.error('Fetch AI prediction details error:', error);
return rejectWithValue(error.message || 'Failed to fetch case details.');
}
}
);
/**
* Update Case Review Async Thunk
*
* Purpose: Update review status of an AI prediction case
*
* @param caseId - Case ID to update
* @param reviewData - Review data
* @param token - Authentication token
* @returns Promise with updated case or error
*/
export const updateCaseReview = createAsyncThunk(
'aiPrediction/updateCaseReview',
async (payload: {
caseId: string;
reviewData: {
review_status: 'pending' | 'reviewed' | 'confirmed' | 'disputed';
reviewed_by?: string;
review_notes?: string;
priority?: 'critical' | 'high' | 'medium' | 'low';
};
token: string;
}, { rejectWithValue }) => {
try {
const response: any = await aiPredictionAPI.updateCaseReview(
payload.caseId,
payload.reviewData,
payload.token
);
if (response.ok && response.data) {
return {
caseId: payload.caseId,
...payload.reviewData,
updated_at: new Date().toISOString()
};
} else {
throw new Error('Failed to update case review');
}
} catch (error: any) {
console.error('Update case review error:', error);
return rejectWithValue(error.message || 'Failed to update case review.');
}
}
);
/**
* Fetch AI Prediction Statistics Async Thunk
*
* Purpose: Fetch statistics for AI predictions dashboard
*
* @param token - Authentication token
* @param timeRange - Time range filter
* @returns Promise with statistics data or error
*/
export const fetchAIPredictionStats = createAsyncThunk(
'aiPrediction/fetchAIPredictionStats',
async (payload: { token: string; timeRange?: 'today' | 'week' | 'month' }, { rejectWithValue }) => {
try {
const response: any = await aiPredictionAPI.getPredictionStats(payload.token, payload.timeRange);
if (response.ok && response.data) {
return response.data as AIPredictionStats;
} else {
// Fallback to mock stats
return generateMockStats();
}
} catch (error: any) {
console.error('Fetch AI prediction stats error:', error);
return rejectWithValue(error.message || 'Failed to fetch statistics.');
}
}
);
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
/**
* Get Priority from AI Prediction
*
* Purpose: Determine case priority based on AI prediction results
*/
function getPriorityFromPrediction(prediction: any): 'critical' | 'high' | 'medium' | 'low' {
if (prediction.clinical_urgency === 'emergency' || prediction.primary_severity === 'high') {
return 'critical';
}
if (prediction.clinical_urgency === 'urgent' || prediction.primary_severity === 'medium') {
return 'high';
}
if (prediction.clinical_urgency === 'moderate' || prediction.primary_severity === 'low') {
return 'medium';
}
return 'low';
}
/**
* Generate Mock AI Predictions
*
* Purpose: Generate mock data for development and testing
*/
function generateMockAIPredictions(): AIPredictionCase[] {
return [
{
patid: "demogw05-08-2017",
hospital_id: "eec24855-d8ae-4fad-8e54-af0480343dc2",
prediction: {
label: "midline shift",
finding_type: "pathology",
clinical_urgency: "urgent",
confidence_score: 0.996,
finding_category: "abnormal",
primary_severity: "high",
anatomical_location: "brain"
},
created_at: "2024-01-15T10:30:00Z",
updated_at: "2024-01-15T10:30:00Z",
review_status: "pending",
priority: "critical"
},
{
patid: "demo-patient-002",
hospital_id: "eec24855-d8ae-4fad-8e54-af0480343dc2",
prediction: {
label: "normal brain",
finding_type: "no_pathology",
clinical_urgency: "routine",
confidence_score: 0.892,
finding_category: "normal",
primary_severity: "none",
anatomical_location: "not_applicable"
},
created_at: "2024-01-15T09:15:00Z",
updated_at: "2024-01-15T09:15:00Z",
review_status: "reviewed",
priority: "low"
},
{
patid: "demo-patient-003",
hospital_id: "eec24855-d8ae-4fad-8e54-af0480343dc2",
prediction: {
label: "hemorrhage",
finding_type: "pathology",
clinical_urgency: "emergency",
confidence_score: 0.945,
finding_category: "critical",
primary_severity: "high",
anatomical_location: "temporal lobe"
},
created_at: "2024-01-15T11:45:00Z",
updated_at: "2024-01-15T11:45:00Z",
review_status: "confirmed",
priority: "critical"
}
];
}
/**
* Generate Mock Statistics
*
* Purpose: Generate mock statistics for development
*/
function generateMockStats(): AIPredictionStats {
return {
totalCases: 156,
criticalCases: 23,
urgentCases: 45,
reviewedCases: 89,
pendingCases: 67,
averageConfidence: 0.887,
todaysCases: 12,
weeklyTrend: 15.4
};
}
// ============================================================================
// INITIAL STATE
// ============================================================================
/**
* Initial AI Prediction State
*
* Purpose: Define the initial state for AI predictions
*
* Features:
* - Prediction cases list and management
* - Current case details
* - Loading states for async operations
* - Error handling and messages
* - Search and filtering
* - Pagination support
* - Cache management
*/
const initialState: AIPredictionState = {
// Prediction data
predictionCases: [],
currentCase: null,
// Loading states
isLoading: false,
isRefreshing: false,
isLoadingCaseDetails: false,
// Error handling
error: null,
// Search and filtering
searchQuery: '',
selectedUrgencyFilter: 'all',
selectedSeverityFilter: 'all',
selectedCategoryFilter: 'all',
sortBy: 'date',
sortOrder: 'desc',
// Pagination
currentPage: 1,
itemsPerPage: 20,
totalItems: 0,
// Cache management
lastUpdated: null,
cacheExpiry: null,
// UI state
showFilters: false,
selectedCaseIds: [],
};
// ============================================================================
// AI PREDICTION SLICE
// ============================================================================
/**
* AI Prediction Slice
*
* Purpose: Redux slice for AI prediction state management
*
* Features:
* - AI prediction data management
* - Search and filtering
* - Case review management
* - Pagination
* - Caching
* - Error handling
* - Loading states
*/
const aiPredictionSlice = createSlice({
name: 'aiPrediction',
initialState,
reducers: {
/**
* Clear Error Action
*
* Purpose: Clear AI prediction errors
*/
clearError: (state) => {
state.error = null;
},
/**
* Set Search Query Action
*
* Purpose: Set search query for AI predictions
*/
setSearchQuery: (state, action: PayloadAction<string>) => {
state.searchQuery = action.payload;
state.currentPage = 1; // Reset to first page when searching
},
/**
* Set Urgency Filter Action
*
* Purpose: Set urgency filter for AI predictions
*/
setUrgencyFilter: (state, action: PayloadAction<AIPredictionState['selectedUrgencyFilter']>) => {
state.selectedUrgencyFilter = action.payload;
state.currentPage = 1; // Reset to first page when filtering
},
/**
* Set Severity Filter Action
*
* Purpose: Set severity filter for AI predictions
*/
setSeverityFilter: (state, action: PayloadAction<AIPredictionState['selectedSeverityFilter']>) => {
state.selectedSeverityFilter = action.payload;
state.currentPage = 1; // Reset to first page when filtering
},
/**
* Set Category Filter Action
*
* Purpose: Set category filter for AI predictions
*/
setCategoryFilter: (state, action: PayloadAction<AIPredictionState['selectedCategoryFilter']>) => {
state.selectedCategoryFilter = action.payload;
state.currentPage = 1; // Reset to first page when filtering
},
/**
* Set Sort Action
*
* Purpose: Set sort options for AI predictions
*/
setSort: (state, action: PayloadAction<{ by: 'date' | 'urgency' | 'confidence' | 'severity'; order: 'asc' | 'desc' }>) => {
state.sortBy = action.payload.by;
state.sortOrder = action.payload.order;
},
/**
* Set Current Page Action
*
* Purpose: Set current page for pagination
*/
setCurrentPage: (state, action: PayloadAction<number>) => {
state.currentPage = action.payload;
},
/**
* Set Items Per Page Action
*
* Purpose: Set items per page for pagination
*/
setItemsPerPage: (state, action: PayloadAction<number>) => {
state.itemsPerPage = action.payload;
state.currentPage = 1; // Reset to first page when changing items per page
},
/**
* Set Current Case Action
*
* Purpose: Set the currently selected AI prediction case
*/
setCurrentCase: (state, action: PayloadAction<AIPredictionCase | null>) => {
state.currentCase = action.payload;
},
/**
* Update Case in List Action
*
* Purpose: Update an AI prediction case in the list
*/
updateCaseInList: (state, action: PayloadAction<AIPredictionCase>) => {
const index = state.predictionCases.findIndex(case_ => case_.patid === action.payload.patid);
if (index !== -1) {
state.predictionCases[index] = action.payload;
}
// Update current case if it's the same case
if (state.currentCase && state.currentCase.patid === action.payload.patid) {
state.currentCase = action.payload;
}
},
/**
* Toggle Show Filters Action
*
* Purpose: Toggle the display of filter options
*/
toggleShowFilters: (state) => {
state.showFilters = !state.showFilters;
},
/**
* Clear All Filters Action
*
* Purpose: Reset all filters to default values
*/
clearAllFilters: (state) => {
state.searchQuery = '';
state.selectedUrgencyFilter = 'all';
state.selectedSeverityFilter = 'all';
state.selectedCategoryFilter = 'all';
state.currentPage = 1;
},
/**
* Select Case Action
*
* Purpose: Add/remove case from selected cases
*/
toggleCaseSelection: (state, action: PayloadAction<string>) => {
const caseId = action.payload;
const index = state.selectedCaseIds.indexOf(caseId);
if (index === -1) {
state.selectedCaseIds.push(caseId);
} else {
state.selectedCaseIds.splice(index, 1);
}
},
/**
* Clear Selected Cases Action
*
* Purpose: Clear all selected cases
*/
clearSelectedCases: (state) => {
state.selectedCaseIds = [];
},
/**
* Clear Cache Action
*
* Purpose: Clear AI prediction data cache
*/
clearCache: (state) => {
state.predictionCases = [];
state.currentCase = null;
state.lastUpdated = null;
state.cacheExpiry = null;
},
},
extraReducers: (builder) => {
// Fetch AI Predictions
builder
.addCase(fetchAIPredictions.pending, (state) => {
state.isLoading = true;
state.error = null;
})
.addCase(fetchAIPredictions.fulfilled, (state, action) => {
state.isLoading = false;
state.predictionCases = action.payload.cases;
state.totalItems = action.payload.total;
state.lastUpdated = new Date().toLocaleString();
state.cacheExpiry = new Date(Date.now() + 5 * 60 * 1000).toLocaleString(); // 5 minutes
state.error = null;
})
.addCase(fetchAIPredictions.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload as string;
});
// Fetch AI Prediction Details
builder
.addCase(fetchAIPredictionDetails.pending, (state) => {
state.isLoadingCaseDetails = true;
state.error = null;
})
.addCase(fetchAIPredictionDetails.fulfilled, (state, action) => {
state.isLoadingCaseDetails = false;
state.currentCase = action.payload;
state.error = null;
})
.addCase(fetchAIPredictionDetails.rejected, (state, action) => {
state.isLoadingCaseDetails = false;
state.error = action.payload as string;
});
// Update Case Review
builder
.addCase(updateCaseReview.fulfilled, (state, action) => {
// Update case in list
const index = state.predictionCases.findIndex(case_ => case_.patid === action.payload.caseId);
if (index !== -1) {
state.predictionCases[index] = {
...state.predictionCases[index],
review_status: action.payload.review_status,
reviewed_by: action.payload.reviewed_by,
priority: action.payload.priority,
updated_at: action.payload.updated_at
};
}
// Update current case if it's the same case
if (state.currentCase && state.currentCase.patid === action.payload.caseId) {
state.currentCase = {
...state.currentCase,
review_status: action.payload.review_status,
reviewed_by: action.payload.reviewed_by,
priority: action.payload.priority,
updated_at: action.payload.updated_at
};
}
})
.addCase(updateCaseReview.rejected, (state, action) => {
state.error = action.payload as string;
});
},
});
// ============================================================================
// EXPORTS
// ============================================================================
export const {
clearError,
setSearchQuery,
setUrgencyFilter,
setSeverityFilter,
setCategoryFilter,
setSort,
setCurrentPage,
setItemsPerPage,
setCurrentCase,
updateCaseInList,
toggleShowFilters,
clearAllFilters,
toggleCaseSelection,
clearSelectedCases,
clearCache,
} = aiPredictionSlice.actions;
export default aiPredictionSlice.reducer;
/*
* End of File: aiPredictionSlice.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,15 @@
/*
* File: index.ts
* Description: Redux exports for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export * from './aiPredictionSlice';
export * from './aiPredictionSelectors';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,749 @@
/*
* File: AIPredictionsScreen.tsx
* Description: Main AI Predictions screen with data rendering and management
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import React, { useEffect, useCallback, useState } from 'react';
import {
View,
Text,
StyleSheet,
FlatList,
RefreshControl,
TouchableOpacity,
Alert,
StatusBar,
SafeAreaView,
} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
import { theme } from '../../../theme';
import { useAppDispatch, useAppSelector } from '../../../store/hooks';
// Import Redux actions and selectors
import {
fetchAIPredictions,
setSearchQuery,
setUrgencyFilter,
setSeverityFilter,
setCategoryFilter,
setCurrentPage,
clearAllFilters,
toggleShowFilters,
toggleCaseSelection,
clearSelectedCases,
updateCaseReview,
} from '../redux';
import {
selectPaginatedCases,
selectIsLoading,
selectError,
selectSearchQuery,
selectUrgencyFilter,
selectSeverityFilter,
selectCategoryFilter,
selectShowFilters,
selectSelectedCaseIds,
selectCasesStatistics,
selectFilterCounts,
selectActiveFiltersCount,
selectCurrentPage,
selectTotalPages,
selectHasNextPage,
selectHasPreviousPage,
} from '../redux';
// Import components
import {
AIPredictionCard,
SearchBar,
FilterTabs,
LoadingState,
EmptyState,
StatsOverview,
} from '../components';
// Import types
import type { AIPredictionCase } from '../types';
// Import auth selector
import { selectUser } from '../../Auth/redux/authSelectors';
// ============================================================================
// INTERFACES
// ============================================================================
interface AIPredictionsScreenProps {
navigation: any;
route?: any;
}
// ============================================================================
// AI PREDICTIONS SCREEN COMPONENT
// ============================================================================
/**
* AIPredictionsScreen Component
*
* Purpose: Main screen for displaying and managing AI prediction cases
*
* Features:
* - Comprehensive AI predictions list
* - Real-time search and filtering
* - Statistics overview dashboard
* - Bulk case selection and actions
* - Pull-to-refresh functionality
* - Pagination support
* - Review status management
* - Modern card-based design
* - Error handling and retry
* - Loading states and empty states
* - Accessibility support
*/
const AIPredictionsScreen: React.FC<AIPredictionsScreenProps> = ({ navigation }) => {
// ============================================================================
// REDUX STATE
// ============================================================================
const dispatch = useAppDispatch();
// Auth state
const user :any = useAppSelector(selectUser);
// AI Prediction state
const cases = useAppSelector(selectPaginatedCases);
const isLoading = useAppSelector(selectIsLoading);
const error = useAppSelector(selectError);
const searchQuery = useAppSelector(selectSearchQuery);
const urgencyFilter = useAppSelector(selectUrgencyFilter);
const severityFilter = useAppSelector(selectSeverityFilter);
const categoryFilter = useAppSelector(selectCategoryFilter);
const showFilters = useAppSelector(selectShowFilters);
const selectedCaseIds = useAppSelector(selectSelectedCaseIds);
const statistics = useAppSelector(selectCasesStatistics);
const filterCounts = useAppSelector(selectFilterCounts);
const activeFiltersCount = useAppSelector(selectActiveFiltersCount);
const currentPage = useAppSelector(selectCurrentPage);
const totalPages = useAppSelector(selectTotalPages);
const hasNextPage = useAppSelector(selectHasNextPage);
const hasPreviousPage = useAppSelector(selectHasPreviousPage);
// ============================================================================
// LOCAL STATE
// ============================================================================
const [refreshing, setRefreshing] = useState(false);
const [showStats, setShowStats] = useState(true);
// ============================================================================
// EFFECTS
// ============================================================================
/**
* Load AI Predictions on Mount
*
* Purpose: Fetch AI predictions when component mounts
*/
console.log('user ===>', user);
useEffect(() => {
if (user?.access_token) {
loadAIPredictions();
}
}, [user?.access_token]);
/**
* Load AI Predictions on Filter Change
*
* Purpose: Reload data when filters change
*/
useEffect(() => {
if (user?.access_token) {
loadAIPredictions();
}
}, [urgencyFilter, severityFilter, categoryFilter, searchQuery, currentPage]);
// ============================================================================
// EVENT HANDLERS
// ============================================================================
/**
* Load AI Predictions
*
* Purpose: Fetch AI predictions from API
*/
const loadAIPredictions = useCallback(async () => {
if (!user?.access_token) return;
try {
const params = {
page: currentPage,
limit: 20,
...(urgencyFilter !== 'all' && { urgency: urgencyFilter }),
...(severityFilter !== 'all' && { severity: severityFilter }),
...(categoryFilter !== 'all' && { category: categoryFilter }),
...(searchQuery.trim() && { search: searchQuery.trim() }),
};
await dispatch(fetchAIPredictions({
token: user.access_token,
params,
})).unwrap();
} catch (error) {
console.error('Failed to load AI predictions:', error);
// Error is handled by Redux state
}
}, [dispatch, user?.access_token, currentPage, urgencyFilter, severityFilter, categoryFilter, searchQuery]);
/**
* Handle Refresh
*
* Purpose: Handle pull-to-refresh
*/
const handleRefresh = useCallback(async () => {
setRefreshing(true);
await loadAIPredictions();
setRefreshing(false);
}, [loadAIPredictions]);
/**
* Handle Search
*
* Purpose: Handle search query change
*/
const handleSearch = useCallback((query: string) => {
dispatch(setSearchQuery(query));
}, [dispatch]);
/**
* Handle Filter Changes
*
* Purpose: Handle filter option changes
*/
const handleUrgencyFilterChange = useCallback((filter: typeof urgencyFilter) => {
dispatch(setUrgencyFilter(filter));
}, [dispatch]);
const handleSeverityFilterChange = useCallback((filter: typeof severityFilter) => {
dispatch(setSeverityFilter(filter));
}, [dispatch]);
const handleCategoryFilterChange = useCallback((filter: typeof categoryFilter) => {
dispatch(setCategoryFilter(filter));
}, [dispatch]);
/**
* Handle Clear Filters
*
* Purpose: Clear all active filters
*/
const handleClearFilters = useCallback(() => {
dispatch(clearAllFilters());
}, [dispatch]);
/**
* Handle Toggle Filters
*
* Purpose: Toggle filter visibility
*/
const handleToggleFilters = useCallback(() => {
dispatch(toggleShowFilters());
}, [dispatch]);
/**
* Handle Case Press
*
* Purpose: Navigate to case details
*/
const handleCasePress = useCallback((predictionCase: AIPredictionCase) => {
navigation.navigate('AIPredictionDetails', { caseId: predictionCase.patid });
}, [navigation]);
/**
* Handle Case Review
*
* Purpose: Handle case review action
*/
const handleCaseReview = useCallback(async (caseId: string) => {
if (!user?.access_token) return;
try {
await dispatch(updateCaseReview({
caseId,
reviewData: {
review_status: 'reviewed',
reviewed_by: user.name || user.email || 'Current User',
},
token: user.access_token,
})).unwrap();
Alert.alert(
'Review Updated',
'Case has been marked as reviewed.',
[{ text: 'OK' }]
);
} catch (error) {
Alert.alert(
'Error',
'Failed to update case review. Please try again.',
[{ text: 'OK' }]
);
}
}, [dispatch, user]);
/**
* Handle Case Selection
*
* Purpose: Handle case selection for bulk operations
*/
const handleCaseSelection = useCallback((caseId: string) => {
dispatch(toggleCaseSelection(caseId));
}, [dispatch]);
/**
* Handle Bulk Actions
*
* Purpose: Handle bulk actions on selected cases
*/
const handleBulkReview = useCallback(() => {
if (selectedCaseIds.length === 0) return;
Alert.alert(
'Bulk Review',
`Mark ${selectedCaseIds.length} cases as reviewed?`,
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Confirm',
onPress: async () => {
// Implement bulk review logic here
// For now, just clear selections
dispatch(clearSelectedCases());
},
},
]
);
}, [selectedCaseIds, dispatch]);
/**
* Handle Page Change
*
* Purpose: Handle pagination
*/
const handlePreviousPage = useCallback(() => {
if (hasPreviousPage) {
dispatch(setCurrentPage(currentPage - 1));
}
}, [dispatch, currentPage, hasPreviousPage]);
const handleNextPage = useCallback(() => {
if (hasNextPage) {
dispatch(setCurrentPage(currentPage + 1));
}
}, [dispatch, currentPage, hasNextPage]);
/**
* Handle Stats Press
*
* Purpose: Handle statistics card press
*/
const handleStatsPress = useCallback((statType: string) => {
// Navigate to detailed statistics or apply relevant filters
switch (statType) {
case 'critical':
dispatch(setUrgencyFilter('emergency'));
break;
case 'urgent':
dispatch(setUrgencyFilter('urgent'));
break;
case 'pending':
// Filter for pending reviews
break;
default:
break;
}
}, [dispatch]);
/**
* Handle Retry
*
* Purpose: Handle retry after error
*/
const handleRetry = useCallback(() => {
loadAIPredictions();
}, [loadAIPredictions]);
// ============================================================================
// RENDER FUNCTIONS
// ============================================================================
/**
* Render AI Prediction Case
*
* Purpose: Render individual AI prediction case card
*/
const renderPredictionCase = useCallback(({ item }: { item: AIPredictionCase }) => (
<AIPredictionCard
predictionCase={item}
onPress={handleCasePress}
onReview={handleCaseReview}
isSelected={selectedCaseIds.includes(item.patid)}
onToggleSelect={handleCaseSelection}
showReviewButton={true}
/>
), [handleCasePress, handleCaseReview, selectedCaseIds, handleCaseSelection]);
/**
* Render List Header
*
* Purpose: Render search, filters, and statistics
*/
const renderListHeader = useCallback(() => (
<View>
{/* Statistics Overview */}
{showStats && (
<StatsOverview
stats={{
totalCases: statistics.total,
criticalCases: statistics.critical,
urgentCases: 0, // Would need to be calculated from urgency filter
reviewedCases: statistics.reviewed,
pendingCases: statistics.pending,
averageConfidence: statistics.averageConfidence,
todaysCases: 0, // Would need to be calculated from today's data
weeklyTrend: 12.5, // Mock data
}}
onStatsPress={handleStatsPress}
/>
)}
{/* Search Bar */}
<SearchBar
value={searchQuery}
onChangeText={handleSearch}
placeholder="Search by patient ID, finding, location..."
/>
{/* Filter Controls */}
<View style={styles.filterControls}>
<TouchableOpacity
style={[styles.filterToggle, showFilters && styles.filterToggleActive]}
onPress={handleToggleFilters}
accessibilityRole="button"
accessibilityLabel="Toggle filters"
>
<Icon name="filter" size={18} color={showFilters ? theme.colors.background : theme.colors.primary} />
<Text style={[styles.filterToggleText, showFilters && styles.filterToggleActiveText]}>
Filters
</Text>
{activeFiltersCount > 0 && (
<View style={styles.filterBadge}>
<Text style={styles.filterBadgeText}>{activeFiltersCount}</Text>
</View>
)}
</TouchableOpacity>
{selectedCaseIds.length > 0 && (
<TouchableOpacity
style={styles.bulkActionButton}
onPress={handleBulkReview}
accessibilityRole="button"
accessibilityLabel={`Bulk actions for ${selectedCaseIds.length} selected cases`}
>
<Icon name="check-circle" size={18} color={theme.colors.background} />
<Text style={styles.bulkActionText}>
Review {selectedCaseIds.length}
</Text>
</TouchableOpacity>
)}
</View>
{/* Filter Tabs */}
{showFilters && (
<FilterTabs
selectedUrgencyFilter={urgencyFilter}
selectedSeverityFilter={severityFilter}
selectedCategoryFilter={categoryFilter}
onUrgencyFilterChange={handleUrgencyFilterChange}
onSeverityFilterChange={handleSeverityFilterChange}
onCategoryFilterChange={handleCategoryFilterChange}
onClearFilters={handleClearFilters}
filterCounts={filterCounts}
activeFiltersCount={activeFiltersCount}
/>
)}
{/* Results Summary */}
<View style={styles.resultsSummary}>
<Text style={styles.resultsText}>
{statistics.total} predictions found
{activeFiltersCount > 0 && ` (${activeFiltersCount} filters applied)`}
</Text>
</View>
</View>
), [
showStats,
statistics,
handleStatsPress,
searchQuery,
handleSearch,
showFilters,
handleToggleFilters,
activeFiltersCount,
selectedCaseIds,
handleBulkReview,
urgencyFilter,
severityFilter,
categoryFilter,
handleUrgencyFilterChange,
handleSeverityFilterChange,
handleCategoryFilterChange,
handleClearFilters,
filterCounts,
]);
/**
* Render List Footer
*
* Purpose: Render pagination controls
*/
const renderListFooter = useCallback(() => {
if (totalPages <= 1) return null;
return (
<View style={styles.paginationContainer}>
<TouchableOpacity
style={[styles.paginationButton, !hasPreviousPage && styles.paginationButtonDisabled]}
onPress={handlePreviousPage}
disabled={!hasPreviousPage}
accessibilityRole="button"
accessibilityLabel="Previous page"
>
<Icon name="chevron-left" size={20} color={hasPreviousPage ? theme.colors.primary : theme.colors.textMuted} />
<Text style={[styles.paginationButtonText, !hasPreviousPage && styles.paginationButtonTextDisabled]}>
Previous
</Text>
</TouchableOpacity>
<Text style={styles.paginationInfo}>
Page {currentPage} of {totalPages}
</Text>
<TouchableOpacity
style={[styles.paginationButton, !hasNextPage && styles.paginationButtonDisabled]}
onPress={handleNextPage}
disabled={!hasNextPage}
accessibilityRole="button"
accessibilityLabel="Next page"
>
<Text style={[styles.paginationButtonText, !hasNextPage && styles.paginationButtonTextDisabled]}>
Next
</Text>
<Icon name="chevron-right" size={20} color={hasNextPage ? theme.colors.primary : theme.colors.textMuted} />
</TouchableOpacity>
</View>
);
}, [totalPages, currentPage, hasPreviousPage, hasNextPage, handlePreviousPage, handleNextPage]);
// ============================================================================
// RENDER
// ============================================================================
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor={theme.colors.background} />
{/* Header */}
<View style={styles.header}>
<Text style={styles.headerTitle}>AI Predictions</Text>
<TouchableOpacity
style={styles.headerButton}
onPress={() => setShowStats(!showStats)}
accessibilityRole="button"
accessibilityLabel="Toggle statistics"
>
<Icon name={showStats ? 'eye-off' : 'eye'} size={20} color={theme.colors.primary} />
</TouchableOpacity>
</View>
{/* Content */}
{error ? (
<EmptyState
title="Error Loading Predictions"
message={error}
iconName="alert-circle"
actionText="Retry"
onAction={handleRetry}
/>
) : isLoading && cases.length === 0 ? (
<LoadingState message="Loading AI predictions..." />
) : cases.length === 0 ? (
<EmptyState
title="No AI Predictions Found"
message="There are no AI prediction cases matching your current filters."
iconName="brain"
actionText="Clear Filters"
onAction={activeFiltersCount > 0 ? handleClearFilters : handleRefresh}
/>
) : (
<FlatList
data={cases}
renderItem={renderPredictionCase}
keyExtractor={(item) => item.patid}
ListHeaderComponent={renderListHeader}
ListFooterComponent={renderListFooter}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={handleRefresh}
colors={[theme.colors.primary]}
tintColor={theme.colors.primary}
/>
}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.listContent}
accessibilityRole="list"
/>
)}
</SafeAreaView>
);
};
// ============================================================================
// STYLES
// ============================================================================
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: theme.colors.background,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.md,
borderBottomWidth: 1,
borderBottomColor: theme.colors.border,
backgroundColor: theme.colors.background,
},
headerTitle: {
fontSize: theme.typography.fontSize.displaySmall,
fontWeight: theme.typography.fontWeight.bold,
color: theme.colors.textPrimary,
},
headerButton: {
padding: theme.spacing.sm,
},
filterControls: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
},
filterToggle: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
borderRadius: theme.borderRadius.medium,
borderWidth: 1,
borderColor: theme.colors.primary,
gap: theme.spacing.sm,
},
filterToggleActive: {
backgroundColor: theme.colors.primary,
},
filterToggleText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.medium,
},
filterToggleActiveText: {
color: theme.colors.background,
},
filterBadge: {
backgroundColor: theme.colors.error,
borderRadius: 10,
minWidth: 20,
height: 20,
justifyContent: 'center',
alignItems: 'center',
},
filterBadgeText: {
fontSize: theme.typography.fontSize.caption,
color: theme.colors.background,
fontWeight: theme.typography.fontWeight.bold,
},
bulkActionButton: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: theme.colors.primary,
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
borderRadius: theme.borderRadius.medium,
gap: theme.spacing.sm,
},
bulkActionText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.background,
fontWeight: theme.typography.fontWeight.medium,
},
resultsSummary: {
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
},
resultsText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
},
listContent: {
paddingBottom: theme.spacing.xl,
},
paginationContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.lg,
marginTop: theme.spacing.lg,
},
paginationButton: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: theme.spacing.md,
paddingVertical: theme.spacing.sm,
borderRadius: theme.borderRadius.medium,
borderWidth: 1,
borderColor: theme.colors.primary,
gap: theme.spacing.xs,
},
paginationButtonDisabled: {
borderColor: theme.colors.textMuted,
opacity: 0.5,
},
paginationButtonText: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.primary,
fontWeight: theme.typography.fontWeight.medium,
},
paginationButtonTextDisabled: {
color: theme.colors.textMuted,
},
paginationInfo: {
fontSize: theme.typography.fontSize.bodyMedium,
color: theme.colors.textSecondary,
fontWeight: theme.typography.fontWeight.medium,
},
});
export default AIPredictionsScreen;
/*
* End of File: AIPredictionsScreen.tsx
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,14 @@
/*
* File: index.ts
* Description: Screens exports for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export { default as AIPredictionsScreen } from './AIPredictionsScreen';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,233 @@
/*
* File: aiPredictionAPI.ts
* Description: API service for AI prediction operations using apisauce
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
import { create } from 'apisauce';
import { API_CONFIG, buildHeaders } from '../../../shared/utils';
const api = create({
baseURL: API_CONFIG.BASE_URL
});
/**
* AI Prediction API Service
*
* Purpose: Handle all AI prediction-related API operations
*
* Features:
* - Get AI prediction results for all patients
* - Get individual case prediction details
* - Update case review status
* - Search and filter predictions
* - Get prediction statistics
*/
export const aiPredictionAPI = {
/**
* Get All AI Prediction Results
*
* Purpose: Fetch all AI prediction results from server
*
* @param token - Authentication token
* @param params - Optional query parameters for filtering
* @returns Promise with AI prediction cases data
*/
getAllPredictions: (token: string, params?: {
page?: number;
limit?: number;
urgency?: string;
severity?: string;
category?: string;
search?: string;
}) => {
const queryParams = params ? { ...params } : {};
return api.get('/api/ai-cases/all-prediction-results', queryParams, buildHeaders({ token }));
},
/**
* Get AI Prediction Case Details
*
* Purpose: Fetch detailed information for a specific AI prediction case
*
* @param caseId - AI prediction case ID (patid)
* @param token - Authentication token
* @returns Promise with detailed case prediction data
*/
getCaseDetails: (caseId: string, token: string) => {
return api.get(`/api/ai-cases/prediction-details/${caseId}`, {}, buildHeaders({ token }));
},
/**
* Update Case Review Status
*
* Purpose: Update the review status of an AI prediction case
*
* @param caseId - AI prediction case ID
* @param reviewData - Review status and notes
* @param token - Authentication token
* @returns Promise with updated case data
*/
updateCaseReview: (caseId: string, reviewData: {
review_status: 'pending' | 'reviewed' | 'confirmed' | 'disputed';
reviewed_by?: string;
review_notes?: string;
priority?: 'critical' | 'high' | 'medium' | 'low';
}, token: string) => {
return api.put(`/api/ai-cases/review/${caseId}`, reviewData, buildHeaders({ token }));
},
/**
* Get Prediction Statistics
*
* Purpose: Fetch AI prediction statistics for dashboard
*
* @param token - Authentication token
* @param timeRange - Optional time range filter (today, week, month)
* @returns Promise with prediction statistics
*/
getPredictionStats: (token: string, timeRange?: 'today' | 'week' | 'month') => {
const params = timeRange ? { timeRange } : {};
return api.get('/api/ai-cases/statistics', params, buildHeaders({ token }));
},
/**
* Search AI Prediction Cases
*
* Purpose: Search AI prediction cases by various criteria
*
* @param query - Search query (patient ID, hospital, findings)
* @param token - Authentication token
* @param filters - Additional search filters
* @returns Promise with search results
*/
searchPredictions: (query: string, token: string, filters?: {
urgency?: string[];
severity?: string[];
category?: string[];
dateRange?: { start: string; end: string };
}) => {
const params = {
q: query,
...(filters && { filters: JSON.stringify(filters) })
};
return api.get('/api/ai-cases/search', params, buildHeaders({ token }));
},
/**
* Get Predictions by Hospital
*
* Purpose: Fetch AI predictions filtered by hospital
*
* @param hospitalId - Hospital UUID
* @param token - Authentication token
* @param params - Optional query parameters
* @returns Promise with hospital-specific predictions
*/
getPredictionsByHospital: (hospitalId: string, token: string, params?: {
page?: number;
limit?: number;
urgency?: string;
startDate?: string;
endDate?: string;
}) => {
const queryParams = params ? { ...params } : {};
return api.get(`/api/ai-cases/hospital/${hospitalId}/predictions`, queryParams, buildHeaders({ token }));
},
/**
* Get Critical Predictions
*
* Purpose: Fetch only critical and urgent AI predictions
*
* @param token - Authentication token
* @returns Promise with critical predictions data
*/
getCriticalPredictions: (token: string) => {
return api.get('/api/ai-cases/critical-predictions', {}, buildHeaders({ token }));
},
/**
* Bulk Update Case Reviews
*
* Purpose: Update multiple case reviews at once
*
* @param caseIds - Array of case IDs to update
* @param reviewData - Review data to apply to all cases
* @param token - Authentication token
* @returns Promise with bulk update results
*/
bulkUpdateReviews: (caseIds: string[], reviewData: {
review_status: 'pending' | 'reviewed' | 'confirmed' | 'disputed';
reviewed_by?: string;
review_notes?: string;
}, token: string) => {
return api.put('/api/ai-cases/bulk-review', {
caseIds,
reviewData
}, buildHeaders({ token }));
},
/**
* Export Predictions Data
*
* Purpose: Export AI predictions data for reporting
*
* @param token - Authentication token
* @param filters - Export filters
* @param format - Export format (csv, xlsx, pdf)
* @returns Promise with export file data
*/
exportPredictions: (token: string, filters?: {
urgency?: string[];
severity?: string[];
dateRange?: { start: string; end: string };
hospitalId?: string;
}, format: 'csv' | 'xlsx' | 'pdf' = 'csv') => {
const params = {
format,
...(filters && { filters: JSON.stringify(filters) })
};
return api.get('/api/ai-cases/export', params, buildHeaders({ token }));
},
/**
* Get Prediction Trends
*
* Purpose: Fetch prediction trends data for analytics
*
* @param token - Authentication token
* @param period - Time period for trends (daily, weekly, monthly)
* @returns Promise with trends data
*/
getPredictionTrends: (token: string, period: 'daily' | 'weekly' | 'monthly' = 'weekly') => {
return api.get('/api/ai-cases/trends', { period }, buildHeaders({ token }));
},
/**
* Submit Feedback on Prediction
*
* Purpose: Submit physician feedback on AI prediction accuracy
*
* @param caseId - AI prediction case ID
* @param feedbackData - Feedback data
* @param token - Authentication token
* @returns Promise with feedback submission result
*/
submitPredictionFeedback: (caseId: string, feedbackData: {
accuracy_rating: 1 | 2 | 3 | 4 | 5;
is_accurate: boolean;
physician_diagnosis?: string;
feedback_notes?: string;
improvement_suggestions?: string;
}, token: string) => {
return api.post(`/api/ai-cases/feedback/${caseId}`, feedbackData, buildHeaders({ token }));
}
};
/*
* End of File: aiPredictionAPI.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,14 @@
/*
* File: index.ts
* Description: Services exports for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export * from './aiPredictionAPI';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,221 @@
/*
* File: aiPrediction.ts
* Description: Type definitions for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
// ============================================================================
// AI PREDICTION INTERFACES
// ============================================================================
/**
* AI Prediction Interface
*
* Purpose: Define the structure of AI prediction data from the API
*
* Based on API response structure:
* - label: Type of medical finding
* - finding_type: Category of the finding
* - clinical_urgency: Urgency level for medical response
* - confidence_score: AI confidence in the prediction (0-1)
* - finding_category: General category of the finding
* - primary_severity: Severity level of the condition
* - anatomical_location: Where the finding is located
*/
export interface AIPrediction {
label: string;
finding_type: 'no_pathology' | 'pathology' | 'abnormal' | 'normal' | 'unknown';
clinical_urgency: 'urgent' | 'moderate' | 'low' | 'routine' | 'emergency';
confidence_score: number; // 0.0 to 1.0
finding_category: 'normal' | 'abnormal' | 'critical' | 'warning' | 'unknown';
primary_severity: 'high' | 'medium' | 'low' | 'none';
anatomical_location: string; // 'not_applicable' | specific location
}
/**
* AI Prediction Case Interface
*
* Purpose: Complete AI prediction case data structure
*
* Features:
* - Patient identification
* - Hospital association
* - AI prediction results
* - Metadata for tracking and display
*/
export interface AIPredictionCase {
patid: string; // Patient ID
hospital_id: string; // Hospital UUID
prediction: AIPrediction;
// Additional metadata (will be added for UI purposes)
created_at?: string;
updated_at?: string;
reviewed_by?: string;
review_status?: 'pending' | 'reviewed' | 'confirmed' | 'disputed';
priority?: 'critical' | 'high' | 'medium' | 'low';
processed_at?: string;
}
/**
* AI Prediction API Response Interface
*
* Purpose: Define the structure of API response
*/
export interface AIPredictionAPIResponse {
success: boolean;
data: AIPredictionCase[];
message?: string;
total?: number;
page?: number;
limit?: number;
}
/**
* AI Prediction State Interface
*
* Purpose: Define Redux state structure for AI predictions
*
* Features:
* - Prediction cases management
* - Current selected case
* - Loading states for async operations
* - Error handling and messages
* - Search and filtering
* - Pagination support
* - Cache management
*/
export interface AIPredictionState {
// Prediction data
predictionCases: AIPredictionCase[];
currentCase: AIPredictionCase | null;
// Loading states
isLoading: boolean;
isRefreshing: boolean;
isLoadingCaseDetails: boolean;
// Error handling
error: string | null;
// Search and filtering
searchQuery: string;
selectedUrgencyFilter: 'all' | 'urgent' | 'moderate' | 'low' | 'routine' | 'emergency';
selectedSeverityFilter: 'all' | 'high' | 'medium' | 'low' | 'none';
selectedCategoryFilter: 'all' | 'normal' | 'abnormal' | 'critical' | 'warning' | 'unknown';
sortBy: 'date' | 'urgency' | 'confidence' | 'severity';
sortOrder: 'asc' | 'desc';
// Pagination
currentPage: number;
itemsPerPage: number;
totalItems: number;
// Cache management
lastUpdated: String | null;
cacheExpiry: String | null;
// UI state
showFilters: boolean;
selectedCaseIds: string[];
}
/**
* AI Prediction Filter Options
*
* Purpose: Define available filter options for the UI
*/
export interface AIPredictionFilters {
urgency: Array<{
label: string;
value: AIPredictionState['selectedUrgencyFilter'];
count?: number;
}>;
severity: Array<{
label: string;
value: AIPredictionState['selectedSeverityFilter'];
count?: number;
}>;
category: Array<{
label: string;
value: AIPredictionState['selectedCategoryFilter'];
count?: number;
}>;
}
/**
* AI Prediction Statistics Interface
*
* Purpose: Define statistics data for dashboard display
*/
export interface AIPredictionStats {
totalCases: number;
criticalCases: number;
urgentCases: number;
reviewedCases: number;
pendingCases: number;
averageConfidence: number;
todaysCases: number;
weeklyTrend: number; // percentage change from last week
}
/**
* AI Prediction Navigation Props
*
* Purpose: Type safety for navigation between AI prediction screens
*/
export type AIPredictionNavigationProps = {
AIPredictionList: undefined;
AIPredictionDetails: { caseId: string };
AIPredictionFilters: undefined;
AIPredictionStats: undefined;
};
// ============================================================================
// UTILITY TYPES
// ============================================================================
/**
* Prediction Urgency Colors
*
* Purpose: Map urgency levels to UI colors
*/
export const URGENCY_COLORS = {
emergency: '#F44336', // Red
urgent: '#FF5722', // Deep Orange
moderate: '#FF9800', // Orange
low: '#FFC107', // Amber
routine: '#4CAF50', // Green
} as const;
/**
* Prediction Severity Colors
*
* Purpose: Map severity levels to UI colors
*/
export const SEVERITY_COLORS = {
high: '#F44336', // Red
medium: '#FF9800', // Orange
low: '#FFC107', // Amber
none: '#4CAF50', // Green
} as const;
/**
* Finding Category Colors
*
* Purpose: Map finding categories to UI colors
*/
export const CATEGORY_COLORS = {
critical: '#F44336', // Red
abnormal: '#FF9800', // Orange
warning: '#FFC107', // Amber
normal: '#4CAF50', // Green
unknown: '#9E9E9E', // Gray
} as const;
/*
* End of File: aiPrediction.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -0,0 +1,14 @@
/*
* File: index.ts
* Description: Type definitions exports for AI Prediction module
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/
export * from './aiPrediction';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions
* Copyright (c) Spurrin Innovations. All rights reserved.
*/

View File

@ -10,6 +10,7 @@ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { theme } from '../theme/theme';
import { DashboardStackNavigator } from '../modules/Dashboard/navigation';
import { SettingsStackNavigator } from '../modules/Settings/navigation';
import { AIPredictionStackNavigator } from '../modules/AIPrediction/navigation';
import { MainTabParamList } from './navigationTypes';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import { ComingSoonScreen } from '../shared/components';
@ -26,7 +27,7 @@ const Tab = createBottomTabNavigator<MainTabParamList>();
* Tab Structure:
* - Dashboard: Main ER dashboard with patient overview and statistics
* - Patients: Detailed patient list and management interface
* - Alerts: Critical notifications and alert management
* - AI Predictions: AI-powered medical case predictions and analysis
* - Reports: Medical reports and documentation access
* - Settings: User preferences and app configuration
*
@ -96,16 +97,15 @@ export const MainTabNavigator: React.FC = () => {
}}
/>
{/* Alerts Tab - Critical notifications */}
{/* AI Predictions Tab - AI-powered medical predictions */}
<Tab.Screen
name="Alerts"
component={ComingSoonScreen} // TODO: Replace with actual AlertsScreen
name="AIPredictions"
component={AIPredictionStackNavigator}
options={{
title: 'Alerts',
tabBarLabel: 'Alerts',
// TODO: Add tab bar icon
title: 'AI Predictions',
tabBarLabel: 'AI Predictions',
tabBarIcon: ({ color, size }) => (
<MaterialIcons name="notifications" size={size} color={color} />
<MaterialIcons name="psychology" size={size} color={color} />
),
headerShown: false,
}}

View File

@ -39,16 +39,14 @@ export type RootStackParamList = {
* Tabs:
* - Dashboard: ER dashboard with patient overview
* - Patients: Patient list and management
* - Alerts: Critical notifications and alerts
* - Reports: Medical reports and documentation
* - AIPredictions: AI-powered medical case predictions and analysis
* - Settings: User preferences and app configuration
*/
export type MainTabParamList = {
Dashboard: DashboardScreenParams; // Dashboard with initial data
Patients: PatientsScreenParams; // Patient list screen
Alerts: AlertsScreenParams; // Alerts screen
Reports: ReportsScreenParams; // Reports screen
Settings: SettingsScreenParams; // Settings screen
Dashboard: DashboardScreenParams; // Dashboard with initial data
Patients: PatientsScreenParams; // Patient list screen
AIPredictions: AIPredictionScreenParams; // AI predictions screen
Settings: SettingsScreenParams; // Settings screen
};
// ============================================================================
@ -86,17 +84,19 @@ export interface PatientsScreenParams {
}
/**
* AlertsScreenParams
* AIPredictionScreenParams
*
* Purpose: Parameters for the alerts screen
* Purpose: Parameters for the AI predictions screen
*
* Parameters:
* - priority: Optional priority filter for alerts
* - unreadOnly: Optional flag to show only unread alerts
* - filter: Optional filter for prediction types
* - urgency: Optional urgency level filter
* - searchQuery: Optional search term for predictions
*/
export interface AlertsScreenParams {
priority?: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW';
unreadOnly?: boolean;
export interface AIPredictionScreenParams {
filter?: 'all' | 'critical' | 'urgent' | 'pending' | 'reviewed';
urgency?: 'emergency' | 'urgent' | 'moderate' | 'low' | 'routine';
searchQuery?: string;
}
/**

View File

@ -12,6 +12,24 @@ export * from './alerts';
export * from './settings';
export * from './common';
// AI Prediction types (re-export from module)
export type {
AIPrediction,
AIPredictionCase,
AIPredictionAPIResponse,
AIPredictionState,
AIPredictionStats,
AIPredictionFilters,
AIPredictionNavigationProps,
} from '../modules/AIPrediction/types';
// AI Prediction constants (re-export from module)
export {
URGENCY_COLORS,
SEVERITY_COLORS,
CATEGORY_COLORS,
} from '../modules/AIPrediction/types';
/*
* End of File: index.ts
* Design & Developed by Tech4Biz Solutions

View File

@ -17,6 +17,7 @@ import alertsReducer from '../modules/Dashboard/redux/alertsSlice';
import settingsReducer from '../modules/Settings/redux/settingsSlice';
import uiReducer from '../modules/Dashboard/redux/uiSlice';
import hospitalReducer from '../modules/Auth/redux/hospitalSlice';
import aiPredictionReducer from '../modules/AIPrediction/redux/aiPredictionSlice';
// ============================================================================
// REDUX PERSIST CONFIGURATION
@ -48,6 +49,7 @@ const persistConfig = {
'auth', // Authentication state (user login, tokens)
'settings', // User preferences and settings
'patientCare', // Patient data cache
'aiPrediction', // AI prediction data cache
],
// Blacklist: Don't persist these reducers
@ -84,6 +86,7 @@ const persistConfig = {
* - auth: Authentication and user management
* - dashboard: ER dashboard data and statistics
* - patientCare: Patient information and medical records
* - aiPrediction: AI prediction cases and analysis
* - alerts: Critical alerts and notifications
* - settings: User preferences and app settings
* - ui: User interface state (loading, modals, etc.)
@ -92,6 +95,7 @@ const rootReducer = combineReducers({
auth: authReducer,
dashboard: dashboardReducer,
patientCare: patientCareReducer,
aiPrediction: aiPredictionReducer,
alerts: alertsReducer,
settings: settingsReducer,
ui: uiReducer,