From e8492cd442d45d5d0a2d6a9167a4185000ddb33a Mon Sep 17 00:00:00 2001 From: yashwin-foxy Date: Fri, 8 Aug 2025 19:03:14 +0530 Subject: [PATCH] ai prediction data mapped to the new tab called ai prediction --- .../__tests__/AIPredictionCard.test.tsx | 249 ++++++ .../__tests__/aiPredictionAPI.test.ts | 361 +++++++++ .../__tests__/aiPredictionSlice.test.ts | 231 ++++++ .../components/AIPredictionCard.tsx | 522 ++++++++++++ .../AIPrediction/components/EmptyState.tsx | 287 +++++++ .../AIPrediction/components/FilterTabs.tsx | 368 +++++++++ .../AIPrediction/components/LoadingState.tsx | 139 ++++ .../AIPrediction/components/SearchBar.tsx | 226 ++++++ .../AIPrediction/components/StatsOverview.tsx | 454 +++++++++++ app/modules/AIPrediction/components/index.ts | 19 + app/modules/AIPrediction/hooks/index.ts | 14 + .../AIPrediction/hooks/useAIPredictions.ts | 383 +++++++++ app/modules/AIPrediction/index.ts | 54 ++ .../navigation/AIPredictionStackNavigator.tsx | 244 ++++++ app/modules/AIPrediction/navigation/index.ts | 16 + .../navigation/navigationTypes.ts | 169 ++++ .../navigation/navigationUtils.ts | 251 ++++++ .../redux/aiPredictionSelectors.ts | 410 ++++++++++ .../AIPrediction/redux/aiPredictionSlice.ts | 621 +++++++++++++++ app/modules/AIPrediction/redux/index.ts | 15 + .../screens/AIPredictionsScreen.tsx | 749 ++++++++++++++++++ app/modules/AIPrediction/screens/index.ts | 14 + .../AIPrediction/services/aiPredictionAPI.ts | 233 ++++++ app/modules/AIPrediction/services/index.ts | 14 + .../AIPrediction/types/aiPrediction.ts | 221 ++++++ app/modules/AIPrediction/types/index.ts | 14 + app/navigation/MainTabNavigator.tsx | 16 +- app/navigation/navigationTypes.ts | 28 +- app/shared/types/index.ts | 18 + app/store/index.ts | 4 + 30 files changed, 6322 insertions(+), 22 deletions(-) create mode 100644 app/modules/AIPrediction/__tests__/AIPredictionCard.test.tsx create mode 100644 app/modules/AIPrediction/__tests__/aiPredictionAPI.test.ts create mode 100644 app/modules/AIPrediction/__tests__/aiPredictionSlice.test.ts create mode 100644 app/modules/AIPrediction/components/AIPredictionCard.tsx create mode 100644 app/modules/AIPrediction/components/EmptyState.tsx create mode 100644 app/modules/AIPrediction/components/FilterTabs.tsx create mode 100644 app/modules/AIPrediction/components/LoadingState.tsx create mode 100644 app/modules/AIPrediction/components/SearchBar.tsx create mode 100644 app/modules/AIPrediction/components/StatsOverview.tsx create mode 100644 app/modules/AIPrediction/components/index.ts create mode 100644 app/modules/AIPrediction/hooks/index.ts create mode 100644 app/modules/AIPrediction/hooks/useAIPredictions.ts create mode 100644 app/modules/AIPrediction/index.ts create mode 100644 app/modules/AIPrediction/navigation/AIPredictionStackNavigator.tsx create mode 100644 app/modules/AIPrediction/navigation/index.ts create mode 100644 app/modules/AIPrediction/navigation/navigationTypes.ts create mode 100644 app/modules/AIPrediction/navigation/navigationUtils.ts create mode 100644 app/modules/AIPrediction/redux/aiPredictionSelectors.ts create mode 100644 app/modules/AIPrediction/redux/aiPredictionSlice.ts create mode 100644 app/modules/AIPrediction/redux/index.ts create mode 100644 app/modules/AIPrediction/screens/AIPredictionsScreen.tsx create mode 100644 app/modules/AIPrediction/screens/index.ts create mode 100644 app/modules/AIPrediction/services/aiPredictionAPI.ts create mode 100644 app/modules/AIPrediction/services/index.ts create mode 100644 app/modules/AIPrediction/types/aiPrediction.ts create mode 100644 app/modules/AIPrediction/types/index.ts diff --git a/app/modules/AIPrediction/__tests__/AIPredictionCard.test.tsx b/app/modules/AIPrediction/__tests__/AIPredictionCard.test.tsx new file mode 100644 index 0000000..4c120e4 --- /dev/null +++ b/app/modules/AIPrediction/__tests__/AIPredictionCard.test.tsx @@ -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( + + ); + + 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(); + expect(getByText('Review')).toBeTruthy(); + }); + + it('should not render review button when showReviewButton is false', () => { + const { queryByText } = render( + + ); + 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( + + ); + expect(queryByText('Review')).toBeNull(); + }); + + it('should render selection checkbox when onToggleSelect is provided', () => { + const { getByRole } = render(); + expect(getByRole('checkbox')).toBeTruthy(); + }); + + it('should show selected state correctly', () => { + const { getByRole } = render( + + ); + + 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(); + + fireEvent.press(getByRole('button')); + expect(mockProps.onPress).toHaveBeenCalledWith(mockPredictionCase); + }); + + it('should call onReview when review button is pressed', () => { + const { getByText } = render(); + + fireEvent.press(getByText('Review')); + expect(mockProps.onReview).toHaveBeenCalledWith('test-patient-001'); + }); + + it('should call onToggleSelect when checkbox is pressed', () => { + const { getByRole } = render(); + + 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(); + expect(getByText('96%')).toBeTruthy(); + }); + + it('should capitalize text correctly', () => { + const { getByText } = render(); + 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( + + ); + + // 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(); + + expect( + getByLabelText('AI Prediction case for patient test-patient-001') + ).toBeTruthy(); + }); + + it('should have proper accessibility hints', () => { + const { getByRole } = render(); + + 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( + + ); + + 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( + + ); + + expect(getByText('Emergency')).toBeTruthy(); + }); + }); +}); + +/* + * End of File: AIPredictionCard.test.tsx + * Design & Developed by Tech4Biz Solutions + * Copyright (c) Spurrin Innovations. All rights reserved. + */ diff --git a/app/modules/AIPrediction/__tests__/aiPredictionAPI.test.ts b/app/modules/AIPrediction/__tests__/aiPredictionAPI.test.ts new file mode 100644 index 0000000..fb85b00 --- /dev/null +++ b/app/modules/AIPrediction/__tests__/aiPredictionAPI.test.ts @@ -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. + */ diff --git a/app/modules/AIPrediction/__tests__/aiPredictionSlice.test.ts b/app/modules/AIPrediction/__tests__/aiPredictionSlice.test.ts new file mode 100644 index 0000000..7975d6c --- /dev/null +++ b/app/modules/AIPrediction/__tests__/aiPredictionSlice.test.ts @@ -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. + */ diff --git a/app/modules/AIPrediction/components/AIPredictionCard.tsx b/app/modules/AIPrediction/components/AIPredictionCard.tsx new file mode 100644 index 0000000..590d57a --- /dev/null +++ b/app/modules/AIPrediction/components/AIPredictionCard.tsx @@ -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 = ({ + 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 ( + + {/* Header Section */} + + + + {predictionCase.patid} + + + {formatDate(predictionCase.processed_at)} + + + + + {onToggleSelect && ( + + + + )} + + + + {capitalize(predictionCase.prediction.clinical_urgency)} + + + + + + {/* Prediction Information */} + + + + {capitalize(predictionCase.prediction.label)} + + + + + {formatConfidence(predictionCase.prediction.confidence_score)} + + + + + {/* Finding Details */} + + + Type: + + {capitalize(predictionCase.prediction.finding_type)} + + + + + Category: + + + {capitalize(predictionCase.prediction.finding_category)} + + + + + + {/* Severity and Location */} + + + + + {capitalize(predictionCase.prediction.primary_severity)} Severity + + + + {predictionCase.prediction.anatomical_location !== 'not_applicable' && ( + + + + {capitalize(predictionCase.prediction.anatomical_location)} + + + )} + + + + {/* Footer Section */} + + + + + {capitalize(predictionCase.review_status || 'pending')} + + + + {predictionCase.reviewed_by && ( + + by {predictionCase.reviewed_by} + + )} + + + {showReviewButton && predictionCase.review_status === 'pending' && ( + + + Review + + )} + + + ); +}; + +// ============================================================================ +// 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. + */ diff --git a/app/modules/AIPrediction/components/EmptyState.tsx b/app/modules/AIPrediction/components/EmptyState.tsx new file mode 100644 index 0000000..81b26d7 --- /dev/null +++ b/app/modules/AIPrediction/components/EmptyState.tsx @@ -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 = ({ + 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 ( + + {/* Empty State Icon */} + + + + + {/* Empty State Title */} + + {title} + + + {/* Empty State Message */} + + {message} + + + {/* Action Buttons */} + + {/* Primary Action Button */} + {(onAction || onRefresh) && ( + + + + {actionText} + + + )} + + {/* Secondary Refresh Button */} + {showRefreshButton && onRefresh && !onAction && ( + + + + Refresh Data + + + )} + + + {/* Suggestions */} + + Try: + + + + Clearing search filters + + + + Adjusting filter criteria + + + + Refreshing the data + + + + + ); +}; + +// ============================================================================ +// 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. + */ diff --git a/app/modules/AIPrediction/components/FilterTabs.tsx b/app/modules/AIPrediction/components/FilterTabs.tsx new file mode 100644 index 0000000..3c1c82d --- /dev/null +++ b/app/modules/AIPrediction/components/FilterTabs.tsx @@ -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; + severity: Record; + category: Record; + }; + 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 = ({ + 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 ( + 0 ? `, ${count} items` : ''}`} + > + {option.color && isSelected && ( + + )} + + + {option.label} + + + {count > 0 && ( + + + {count} + + + )} + + ); + }; + + // ============================================================================ + // RENDER + // ============================================================================ + + return ( + + {/* Header with Clear Filters */} + + Filters + + {activeFiltersCount > 0 && ( + + + Clear All + + )} + + + {/* Urgency Filters */} + + Clinical Urgency + + {URGENCY_FILTERS.map((option) => + renderFilterTab( + { ...option, count: getFilterCount('urgency', option.value) }, + selectedUrgencyFilter === option.value, + () => onUrgencyFilterChange(option.value as AIPredictionState['selectedUrgencyFilter']), + 'urgency' + ) + )} + + + + {/* Severity Filters */} + + Primary Severity + + {SEVERITY_FILTERS.map((option) => + renderFilterTab( + { ...option, count: getFilterCount('severity', option.value) }, + selectedSeverityFilter === option.value, + () => onSeverityFilterChange(option.value as AIPredictionState['selectedSeverityFilter']), + 'severity' + ) + )} + + + + {/* Category Filters */} + + Finding Category + + {CATEGORY_FILTERS.map((option) => + renderFilterTab( + { ...option, count: getFilterCount('category', option.value) }, + selectedCategoryFilter === option.value, + () => onCategoryFilterChange(option.value as AIPredictionState['selectedCategoryFilter']), + 'category' + ) + )} + + + + ); +}; + +// ============================================================================ +// 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. + */ diff --git a/app/modules/AIPrediction/components/LoadingState.tsx b/app/modules/AIPrediction/components/LoadingState.tsx new file mode 100644 index 0000000..4331d7c --- /dev/null +++ b/app/modules/AIPrediction/components/LoadingState.tsx @@ -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 = ({ + message = 'Loading AI predictions...', + showSpinner = true, + size = 'large', + style, +}) => { + // ============================================================================ + // RENDER + // ============================================================================ + + return ( + + {/* Loading Spinner */} + {showSpinner && ( + + )} + + {/* Loading Message */} + + {message} + + + {/* Loading Animation Dots */} + + + + + + + ); +}; + +// ============================================================================ +// 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. + */ diff --git a/app/modules/AIPrediction/components/SearchBar.tsx b/app/modules/AIPrediction/components/SearchBar.tsx new file mode 100644 index 0000000..46af82a --- /dev/null +++ b/app/modules/AIPrediction/components/SearchBar.tsx @@ -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 = ({ + 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 ( + + {/* Search Icon */} + + + {/* Text Input */} + + + {/* Clear Button */} + {value.length > 0 && !disabled && ( + + + + )} + + ); +}; + +// ============================================================================ +// 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. + */ diff --git a/app/modules/AIPrediction/components/StatsOverview.tsx b/app/modules/AIPrediction/components/StatsOverview.tsx new file mode 100644 index 0000000..17a4e03 --- /dev/null +++ b/app/modules/AIPrediction/components/StatsOverview.tsx @@ -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 = ({ + title, + value, + subtitle, + iconName, + color, + onPress, + trend, + isPercentage = false, +}) => { + const displayValue = typeof value === 'number' + ? isPercentage + ? `${Math.round(value * 100)}%` + : value.toLocaleString() + : value; + + return ( + + {/* Card Header */} + + + + + + {displayValue} + + + + {trend !== undefined && ( + + = 0 ? 'trending-up' : 'trending-down'} + size={14} + color={trend >= 0 ? theme.colors.success : theme.colors.error} + /> + = 0 ? theme.colors.success : theme.colors.error } + ]}> + {Math.abs(trend).toFixed(1)}% + + + )} + + + {/* Card Content */} + + + {title} + {subtitle && ( + {subtitle} + )} + + + ); +}; + +// ============================================================================ +// 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 = ({ + 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 ( + + + AI Predictions Overview + + + Loading statistics... + + + ); + } + + return ( + + {/* Section Header */} + + AI Predictions Overview + handleStatPress('all')} + accessibilityRole="button" + accessibilityLabel="View all statistics" + > + View All + + + + + {/* Statistics Grid */} + + {/* Total Cases */} + handleStatPress('total')} + /> + + {/* Critical Cases */} + handleStatPress('critical')} + /> + + {/* Urgent Cases */} + handleStatPress('urgent')} + /> + + {/* Reviewed Cases */} + handleStatPress('reviewed')} + /> + + {/* Pending Cases */} + handleStatPress('pending')} + /> + + {/* Average Confidence */} + handleStatPress('confidence')} + isPercentage={true} + /> + + {/* Today's Cases */} + handleStatPress('today')} + /> + + {/* Weekly Trend */} + = 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} + /> + + + {/* Summary Section */} + + + + + Quick Insights + + + + + Review Progress: + + {Math.round((stats.reviewedCases / stats.totalCases) * 100)}% + + + + + Critical Rate: + + {Math.round((stats.criticalCases / stats.totalCases) * 100)}% + + + + + Daily Average: + + {Math.round(stats.totalCases / 7)} cases + + + + + + + ); +}; + +// ============================================================================ +// 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. + */ diff --git a/app/modules/AIPrediction/components/index.ts b/app/modules/AIPrediction/components/index.ts new file mode 100644 index 0000000..dbc9ef2 --- /dev/null +++ b/app/modules/AIPrediction/components/index.ts @@ -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. + */ diff --git a/app/modules/AIPrediction/hooks/index.ts b/app/modules/AIPrediction/hooks/index.ts new file mode 100644 index 0000000..592bfb7 --- /dev/null +++ b/app/modules/AIPrediction/hooks/index.ts @@ -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. + */ diff --git a/app/modules/AIPrediction/hooks/useAIPredictions.ts b/app/modules/AIPrediction/hooks/useAIPredictions.ts new file mode 100644 index 0000000..613b0b4 --- /dev/null +++ b/app/modules/AIPrediction/hooks/useAIPredictions.ts @@ -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; + statistics: ReturnType; + + // 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; + refreshPredictions: () => Promise; + 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; + + // 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. + */ diff --git a/app/modules/AIPrediction/index.ts b/app/modules/AIPrediction/index.ts new file mode 100644 index 0000000..920e1d5 --- /dev/null +++ b/app/modules/AIPrediction/index.ts @@ -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. + */ diff --git a/app/modules/AIPrediction/navigation/AIPredictionStackNavigator.tsx b/app/modules/AIPrediction/navigation/AIPredictionStackNavigator.tsx new file mode 100644 index 0000000..20c1bbe --- /dev/null +++ b/app/modules/AIPrediction/navigation/AIPredictionStackNavigator.tsx @@ -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(); + +// ============================================================================ +// HEADER COMPONENTS +// ============================================================================ + +/** + * Header Back Button + * + * Purpose: Custom back button for navigation header + */ +const HeaderBackButton: React.FC<{ onPress: () => void }> = ({ onPress }) => ( + + + +); + +/** + * Header Action Button + * + * Purpose: Custom action button for navigation header + */ +const HeaderActionButton: React.FC<{ + iconName: string; + onPress: () => void; + accessibilityLabel?: string; +}> = ({ iconName, onPress, accessibilityLabel }) => ( + + + +); + +// ============================================================================ +// 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 ( + + {/* AI Prediction List Screen */} + ({ + title: 'AI Predictions', + headerLeft: () => null, // No back button on main screen + headerRight: () => ( + { + // Open options menu + // For now, just navigate to stats + // @ts-ignore + navigation.navigate('AIPredictionStats'); + }} + accessibilityLabel="More options" + /> + ), + })} + /> + + {/* AI Prediction Details Screen */} + ({ + title: 'Prediction Details', + headerLeft: () => ( + navigation.goBack()} /> + ), + headerRight: () => ( + { + // Share prediction details + console.log('Share prediction:', route.params?.caseId); + }} + accessibilityLabel="Share prediction" + /> + ), + })} + /> + + {/* AI Prediction Filters Screen */} + ({ + title: 'Advanced Filters', + headerLeft: () => ( + navigation.goBack()} /> + ), + headerRight: () => ( + { + // Reset filters + console.log('Reset filters'); + }} + accessibilityLabel="Reset filters" + /> + ), + })} + /> + + {/* AI Prediction Stats Screen */} + ({ + title: 'Statistics', + headerLeft: () => ( + navigation.goBack()} /> + ), + headerRight: () => ( + { + // Export statistics + console.log('Export stats:', route.params?.timeRange); + }} + accessibilityLabel="Export statistics" + /> + ), + })} + /> + + ); +}; + +// ============================================================================ +// 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. + */ diff --git a/app/modules/AIPrediction/navigation/index.ts b/app/modules/AIPrediction/navigation/index.ts new file mode 100644 index 0000000..5094ddf --- /dev/null +++ b/app/modules/AIPrediction/navigation/index.ts @@ -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. + */ diff --git a/app/modules/AIPrediction/navigation/navigationTypes.ts b/app/modules/AIPrediction/navigation/navigationTypes.ts new file mode 100644 index 0000000..e590439 --- /dev/null +++ b/app/modules/AIPrediction/navigation/navigationTypes.ts @@ -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. + */ diff --git a/app/modules/AIPrediction/navigation/navigationUtils.ts b/app/modules/AIPrediction/navigation/navigationUtils.ts new file mode 100644 index 0000000..7dc12a3 --- /dev/null +++ b/app/modules/AIPrediction/navigation/navigationUtils.ts @@ -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. + */ diff --git a/app/modules/AIPrediction/redux/aiPredictionSelectors.ts b/app/modules/AIPrediction/redux/aiPredictionSelectors.ts new file mode 100644 index 0000000..aa7f222 --- /dev/null +++ b/app/modules/AIPrediction/redux/aiPredictionSelectors.ts @@ -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. + */ diff --git a/app/modules/AIPrediction/redux/aiPredictionSlice.ts b/app/modules/AIPrediction/redux/aiPredictionSlice.ts new file mode 100644 index 0000000..005e484 --- /dev/null +++ b/app/modules/AIPrediction/redux/aiPredictionSlice.ts @@ -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) => { + 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) => { + 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) => { + 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) => { + 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) => { + state.currentPage = action.payload; + }, + + /** + * Set Items Per Page Action + * + * Purpose: Set items per page for pagination + */ + setItemsPerPage: (state, action: PayloadAction) => { + 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) => { + state.currentCase = action.payload; + }, + + /** + * Update Case in List Action + * + * Purpose: Update an AI prediction case in the list + */ + updateCaseInList: (state, action: PayloadAction) => { + 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) => { + 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. + */ diff --git a/app/modules/AIPrediction/redux/index.ts b/app/modules/AIPrediction/redux/index.ts new file mode 100644 index 0000000..f93e701 --- /dev/null +++ b/app/modules/AIPrediction/redux/index.ts @@ -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. + */ diff --git a/app/modules/AIPrediction/screens/AIPredictionsScreen.tsx b/app/modules/AIPrediction/screens/AIPredictionsScreen.tsx new file mode 100644 index 0000000..acff58c --- /dev/null +++ b/app/modules/AIPrediction/screens/AIPredictionsScreen.tsx @@ -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 = ({ 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 }) => ( + + ), [handleCasePress, handleCaseReview, selectedCaseIds, handleCaseSelection]); + + /** + * Render List Header + * + * Purpose: Render search, filters, and statistics + */ + const renderListHeader = useCallback(() => ( + + {/* Statistics Overview */} + {showStats && ( + + )} + + {/* Search Bar */} + + + {/* Filter Controls */} + + + + + Filters + + {activeFiltersCount > 0 && ( + + {activeFiltersCount} + + )} + + + {selectedCaseIds.length > 0 && ( + + + + Review {selectedCaseIds.length} + + + )} + + + {/* Filter Tabs */} + {showFilters && ( + + )} + + {/* Results Summary */} + + + {statistics.total} predictions found + {activeFiltersCount > 0 && ` (${activeFiltersCount} filters applied)`} + + + + ), [ + 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 ( + + + + + Previous + + + + + Page {currentPage} of {totalPages} + + + + + Next + + + + + ); + }, [totalPages, currentPage, hasPreviousPage, hasNextPage, handlePreviousPage, handleNextPage]); + + // ============================================================================ + // RENDER + // ============================================================================ + + return ( + + + + {/* Header */} + + AI Predictions + setShowStats(!showStats)} + accessibilityRole="button" + accessibilityLabel="Toggle statistics" + > + + + + + {/* Content */} + {error ? ( + + ) : isLoading && cases.length === 0 ? ( + + ) : cases.length === 0 ? ( + 0 ? handleClearFilters : handleRefresh} + /> + ) : ( + item.patid} + ListHeaderComponent={renderListHeader} + ListFooterComponent={renderListFooter} + refreshControl={ + + } + showsVerticalScrollIndicator={false} + contentContainerStyle={styles.listContent} + accessibilityRole="list" + /> + )} + + ); +}; + +// ============================================================================ +// 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. + */ diff --git a/app/modules/AIPrediction/screens/index.ts b/app/modules/AIPrediction/screens/index.ts new file mode 100644 index 0000000..2a13828 --- /dev/null +++ b/app/modules/AIPrediction/screens/index.ts @@ -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. + */ diff --git a/app/modules/AIPrediction/services/aiPredictionAPI.ts b/app/modules/AIPrediction/services/aiPredictionAPI.ts new file mode 100644 index 0000000..1a131c1 --- /dev/null +++ b/app/modules/AIPrediction/services/aiPredictionAPI.ts @@ -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. + */ diff --git a/app/modules/AIPrediction/services/index.ts b/app/modules/AIPrediction/services/index.ts new file mode 100644 index 0000000..e410e41 --- /dev/null +++ b/app/modules/AIPrediction/services/index.ts @@ -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. + */ diff --git a/app/modules/AIPrediction/types/aiPrediction.ts b/app/modules/AIPrediction/types/aiPrediction.ts new file mode 100644 index 0000000..acf9582 --- /dev/null +++ b/app/modules/AIPrediction/types/aiPrediction.ts @@ -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. + */ diff --git a/app/modules/AIPrediction/types/index.ts b/app/modules/AIPrediction/types/index.ts new file mode 100644 index 0000000..ea9fa52 --- /dev/null +++ b/app/modules/AIPrediction/types/index.ts @@ -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. + */ diff --git a/app/navigation/MainTabNavigator.tsx b/app/navigation/MainTabNavigator.tsx index a199b1a..fd044e0 100644 --- a/app/navigation/MainTabNavigator.tsx +++ b/app/navigation/MainTabNavigator.tsx @@ -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(); * 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 */} ( - + ), headerShown: false, }} diff --git a/app/navigation/navigationTypes.ts b/app/navigation/navigationTypes.ts index 2367d1a..80c2cc6 100644 --- a/app/navigation/navigationTypes.ts +++ b/app/navigation/navigationTypes.ts @@ -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; } /** diff --git a/app/shared/types/index.ts b/app/shared/types/index.ts index 4b879ce..5cc6930 100644 --- a/app/shared/types/index.ts +++ b/app/shared/types/index.ts @@ -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 diff --git a/app/store/index.ts b/app/store/index.ts index 081d0c2..e442e37 100644 --- a/app/store/index.ts +++ b/app/store/index.ts @@ -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,