diff --git a/frontend/src/assets/icons/back.svg b/frontend/src/assets/icons/back.svg new file mode 100644 index 00000000..3db41f10 --- /dev/null +++ b/frontend/src/assets/icons/back.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/frontend/src/common/hooks/useChat.ts b/frontend/src/common/hooks/useChat.ts index 6538355c..5e65e25c 100644 --- a/frontend/src/common/hooks/useChat.ts +++ b/frontend/src/common/hooks/useChat.ts @@ -21,8 +21,8 @@ export function useChat(sessionId?: string) { // Mutation for creating a new session const createSession = useMutation({ mutationFn: () => bedrockService.createChatSession(), - onSuccess: (newSessionId) => { - queryClient.invalidateQueries({ queryKey: [QueryKey.Chat, 'sessions'] }); + onSuccess: async (newSessionId) => { + await queryClient.invalidateQueries({ queryKey: [QueryKey.Chat, 'sessions'] }); return newSessionId; }, }); @@ -33,9 +33,9 @@ export function useChat(sessionId?: string) { if (!sessionId) throw new Error('No active session'); return bedrockService.sendMessage(sessionId, message); }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [QueryKey.Chat, sessionId] }); - queryClient.invalidateQueries({ queryKey: [QueryKey.Chat, 'sessions'] }); + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: [QueryKey.Chat, sessionId] }); + await queryClient.invalidateQueries({ queryKey: [QueryKey.Chat, 'sessions'] }); }, }); diff --git a/frontend/src/pages/Auth/SignIn/api/useSignIn.ts b/frontend/src/pages/Auth/SignIn/api/useSignIn.ts index 7ff8bddf..e78198ef 100644 --- a/frontend/src/pages/Auth/SignIn/api/useSignIn.ts +++ b/frontend/src/pages/Auth/SignIn/api/useSignIn.ts @@ -60,9 +60,9 @@ export const useSignIn = () => { return useMutation({ mutationFn: signIn, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [QueryKey.UserTokens] }); - queryClient.invalidateQueries({ queryKey: [QueryKey.Users, 'current'] }); + onSuccess: async () => { + await queryClient.invalidateQueries({ queryKey: [QueryKey.UserTokens] }); + await queryClient.invalidateQueries({ queryKey: [QueryKey.Users, 'current'] }); }, }); }; diff --git a/frontend/src/pages/Processing/ProcessingPage.tsx b/frontend/src/pages/Processing/ProcessingPage.tsx index 8a1367b1..91a01037 100644 --- a/frontend/src/pages/Processing/ProcessingPage.tsx +++ b/frontend/src/pages/Processing/ProcessingPage.tsx @@ -73,10 +73,12 @@ const ProcessingPage: React.FC = () => { console.log('Processing complete'); - queryClient.invalidateQueries({ queryKey: [QueryKey.Reports] }); - queryClient.invalidateQueries({ queryKey: [QueryKey.LatestReports] }); + await queryClient.invalidateQueries({ queryKey: [QueryKey.Reports] }); + await queryClient.invalidateQueries({ queryKey: [QueryKey.LatestReports] }); - history.push(`/tabs/reports/${reportId}`); + history.push(`/tabs/reports/${reportId}`, { + from: location.pathname, + }); } else if (data.status === 'failed') { if (data.isMedicalReport === false) { setIsProcessing(false); diff --git a/frontend/src/pages/ReportDetail/ReportDetailPage.scss b/frontend/src/pages/ReportDetail/ReportDetailPage.scss index 35d2f94b..2b724ef5 100644 --- a/frontend/src/pages/ReportDetail/ReportDetailPage.scss +++ b/frontend/src/pages/ReportDetail/ReportDetailPage.scss @@ -25,6 +25,19 @@ color: #313e4c; } + &__back-button { + border: none; + background: transparent; + color: #435ff0; + font-size: 24px; + padding: 0; + cursor: pointer; + display: flex; + align-items: center; + margin-right: 0.9em; + margin-bottom: 1em; + } + &__close-button { border: none; background: transparent; diff --git a/frontend/src/pages/ReportDetail/ReportDetailPage.tsx b/frontend/src/pages/ReportDetail/ReportDetailPage.tsx index 9b445bc3..9847443b 100644 --- a/frontend/src/pages/ReportDetail/ReportDetailPage.tsx +++ b/frontend/src/pages/ReportDetail/ReportDetailPage.tsx @@ -1,13 +1,12 @@ import { IonPage, IonContent } from '@ionic/react'; -import { useState } from 'react'; +import { FC, useState } from 'react'; import { useHistory, useParams } from 'react-router-dom'; import './ReportDetailPage.scss'; -import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; import { MedicalReport } from '../../common/models/medicalReport'; import { useTranslation } from 'react-i18next'; import { getAuthConfig } from 'common/api/reportService'; -import { useToasts } from 'common/hooks/useToasts'; import AiAssistantNotice from './components/AiAssistantNotice'; import ReportHeader from './components/ReportHeader'; import ReportTabs from './components/ReportTabs'; @@ -20,42 +19,47 @@ import { QueryKey } from 'common/utils/constants'; const API_URL = import.meta.env.VITE_BASE_URL_API || ''; -// Function to fetch report by ID -const fetchReportById = async (id: string): Promise => { - const response = await axios.get( - `${API_URL}/api/reports/${id}`, - await getAuthConfig(), - ); - return response.data; -}; - /** * Page component for displaying detailed medical report analysis. * This shows AI insights and original report data with flagged values. */ -const ReportDetailPage: React.FC = () => { +const ReportDetailPage: FC = () => { const { reportId } = useParams<{ reportId: string }>(); const history = useHistory(); const { t } = useTranslation(); - const { createToast } = useToasts(); const [isUploadModalOpen, setIsUploadModalOpen] = useState(false); - const queryClient = useQueryClient(); + const [activeTab, setActiveTab] = useState<'ai' | 'original'>('ai'); const handleUploadComplete = () => { setIsUploadModalOpen(false); history.push('/tabs/home'); }; + // Handle tab selection + const handleTabChange = (tab: 'ai' | 'original') => { + setActiveTab(tab); + }; + + // Function to fetch report by ID + const fetchReportById = async (id: string): Promise => { + const response = await axios.get( + `${API_URL}/api/reports/${id}`, + await getAuthConfig(), + ); + return response.data; + }; + // Fetch report data using react-query - const { data, isLoading, error } = useQuery({ + const { + data: reportData, + isLoading, + error, + } = useQuery({ queryKey: [QueryKey.ReportDetail, reportId], queryFn: () => fetchReportById(reportId!), enabled: !!reportId, }); - // State to track expanded sections - const [activeTab, setActiveTab] = useState<'ai' | 'original'>('ai'); - // Handle loading and error states if (isLoading) { return ; @@ -71,7 +75,7 @@ const ReportDetailPage: React.FC = () => { ); } - if (!data) { + if (!reportData) { return ( @@ -81,77 +85,11 @@ const ReportDetailPage: React.FC = () => { ); } - const reportData = data; - - // Handle tab selection - const handleTabChange = (tab: 'ai' | 'original') => { - setActiveTab(tab); - }; - - // Handle close button - const handleClose = () => { - history.push('/tabs/home'); - }; - - // Handle action buttons - const handleDiscard = async (setIsProcessing: (isProcessing: boolean) => void) => { - try { - setIsProcessing(true); - await axios.delete(`${API_URL}/api/reports/${reportId}`, await getAuthConfig()); - setIsProcessing(false); - - // Show toast notification - createToast({ - message: t('report.discard.success', { - ns: 'reportDetail', - defaultValue: 'Report deleted successfully', - }), - duration: 2000, - }); - - queryClient.invalidateQueries({ queryKey: [QueryKey.Reports] }); - queryClient.invalidateQueries({ queryKey: [QueryKey.LatestReports] }); - queryClient.invalidateQueries({ queryKey: [QueryKey.ReportDetail, reportId] }); - - // Navigate back - history.push('/tabs/home'); - } catch (error) { - setIsProcessing(false); - - console.error('Error discarding report:', error); - createToast({ - message: t('report.discard.error', { - ns: 'reportDetail', - defaultValue: 'Failed to delete report', - }), - duration: 2000, - color: 'danger', - }); - } - }; - - const handleNewUpload = async (setIsProcessing: (isProcessing: boolean) => void) => { - try { - setIsProcessing(true); - await axios.delete(`${API_URL}/api/reports/${reportId}`, await getAuthConfig()); - setIsProcessing(false); - - queryClient.invalidateQueries({ queryKey: [QueryKey.Reports] }); - queryClient.invalidateQueries({ queryKey: [QueryKey.LatestReports] }); - queryClient.invalidateQueries({ queryKey: [QueryKey.ReportDetail, reportId] }); - - setIsUploadModalOpen(true); - } catch (error) { - setIsProcessing(false); - console.error('Error deleting report before new upload:', error); - } - }; - return ( {/* Header component */} - + {/* Tab selector for AI Insights vs Original Report */} @@ -172,9 +110,9 @@ const ReportDetailPage: React.FC = () => { {/* Action buttons at the bottom */} void) => Promise; - onNewUpload: (setIsProcessing: (isProcessing: boolean) => void) => Promise; - reportTitle?: string; - reportId?: string; + reportId: string; + reportTitle: string; + setIsUploadModalOpen: (isOpen: boolean) => void; } -const ActionButtons: React.FC = ({ onDiscard, onNewUpload, reportTitle }) => { +const ActionButtons: FC = ({ reportId, reportTitle, setIsUploadModalOpen }) => { const { t } = useTranslation(['reportDetail', 'common']); + const { createToast } = useToasts(); + const history = useHistory(); + const queryClient = useQueryClient(); const [showConfirmDiscard, setShowConfirmDiscard] = useState(false); const [showConfirmNewUpload, setShowConfirmNewUpload] = useState(false); const [isProcessing, setIsProcessing] = useState(false); @@ -22,7 +32,7 @@ const ActionButtons: React.FC = ({ onDiscard, onNewUpload, r const handleConfirmDiscard = async () => { setShowConfirmDiscard(false); - await onDiscard(setIsProcessing); + await handleDiscard(); }; const handleCancelDiscard = () => { @@ -36,13 +46,67 @@ const ActionButtons: React.FC = ({ onDiscard, onNewUpload, r const handleConfirmNewUpload = async () => { setShowConfirmNewUpload(false); - await onNewUpload(setIsProcessing); + await handleNewUpload(); }; const handleCancelNewUpload = () => { setShowConfirmNewUpload(false); }; + // Handle action buttons + const handleDiscard = async () => { + try { + setIsProcessing(true); + await axios.delete(`${API_URL}/api/reports/${reportId}`, await getAuthConfig()); + setIsProcessing(false); + + // Show toast notification + createToast({ + message: t('report.discard.success', { + ns: 'reportDetail', + defaultValue: 'Report deleted successfully', + }), + duration: 2000, + }); + + await queryClient.invalidateQueries({ queryKey: [QueryKey.Reports] }); + await queryClient.invalidateQueries({ queryKey: [QueryKey.LatestReports] }); + await queryClient.invalidateQueries({ queryKey: [QueryKey.ReportDetail, reportId] }); + + // Navigate back + history.push('/tabs/home'); + } catch (error) { + setIsProcessing(false); + + console.error('Error discarding report:', error); + createToast({ + message: t('report.discard.error', { + ns: 'reportDetail', + defaultValue: 'Failed to delete report', + }), + duration: 2000, + color: 'danger', + }); + } + }; + + const handleNewUpload = async () => { + try { + setIsProcessing(true); + await axios.delete(`${API_URL}/api/reports/${reportId}`, await getAuthConfig()); + setIsProcessing(false); + + await queryClient.invalidateQueries({ queryKey: [QueryKey.Reports] }); + await queryClient.invalidateQueries({ queryKey: [QueryKey.LatestReports] }); + await queryClient.invalidateQueries({ queryKey: [QueryKey.ReportDetail, reportId] }); + + setIsUploadModalOpen(true); + } catch (error) { + setIsProcessing(false); + console.error('Error deleting report before new upload:', error); + } + }; + return ( <>
diff --git a/frontend/src/pages/ReportDetail/components/AiAnalysisTab.tsx b/frontend/src/pages/ReportDetail/components/AiAnalysisTab.tsx index fe6217cc..702dd04c 100644 --- a/frontend/src/pages/ReportDetail/components/AiAnalysisTab.tsx +++ b/frontend/src/pages/ReportDetail/components/AiAnalysisTab.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { FC, useState } from 'react'; import { MedicalReport, LabValue } from '../../../common/models/medicalReport'; import EmergencyAlert from './EmergencyAlert'; import FlaggedValuesSection from './FlaggedValuesSection'; @@ -10,13 +10,10 @@ interface AiAnalysisTabProps { isEmergencyAlertVisible?: boolean; } -const AiAnalysisTab: React.FC = ({ - reportData, - isEmergencyAlertVisible = true, -}) => { +const AiAnalysisTab: FC = ({ reportData, isEmergencyAlertVisible = true }) => { // State to track expanded sections - const [flaggedValuesExpanded, setFlaggedValuesExpanded] = React.useState(true); - const [normalValuesExpanded, setNormalValuesExpanded] = React.useState(true); + const [flaggedValuesExpanded, setFlaggedValuesExpanded] = useState(true); + const [normalValuesExpanded, setNormalValuesExpanded] = useState(true); // Toggle expanded state of sections const toggleFlaggedValues = () => setFlaggedValuesExpanded(!flaggedValuesExpanded); diff --git a/frontend/src/pages/ReportDetail/components/AiAssistantNotice.tsx b/frontend/src/pages/ReportDetail/components/AiAssistantNotice.tsx index f257a1b3..918690d8 100644 --- a/frontend/src/pages/ReportDetail/components/AiAssistantNotice.tsx +++ b/frontend/src/pages/ReportDetail/components/AiAssistantNotice.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { FC, useState } from 'react'; import { IonText } from '@ionic/react'; import AIAssistantModal from '../../../common/components/AIAssistant/AIAssistantModal'; import './AiAssistantNotice.scss'; @@ -6,7 +6,7 @@ import './AiAssistantNotice.scss'; /** * Component to display an AI Assistant notice with a link to open the AI Chat modal. */ -const AiAssistantNotice: React.FC = () => { +const AiAssistantNotice: FC = () => { const [isAIAssistantOpen, setIsAIAssistantOpen] = useState(false); const handleOpenAIAssistant = () => { diff --git a/frontend/src/pages/ReportDetail/components/EmergencyAlert.tsx b/frontend/src/pages/ReportDetail/components/EmergencyAlert.tsx index 1c63ce20..595ff265 100644 --- a/frontend/src/pages/ReportDetail/components/EmergencyAlert.tsx +++ b/frontend/src/pages/ReportDetail/components/EmergencyAlert.tsx @@ -1,8 +1,8 @@ -import React from 'react'; +import { FC } from 'react'; import { useTranslation } from 'react-i18next'; import redAlertIcon from 'assets/icons/red-alert.svg'; -const EmergencyAlert: React.FC = () => { +const EmergencyAlert: FC = () => { const { t } = useTranslation(); return ( diff --git a/frontend/src/pages/ReportDetail/components/FlaggedValuesSection.tsx b/frontend/src/pages/ReportDetail/components/FlaggedValuesSection.tsx index 6ab131b9..305d200e 100644 --- a/frontend/src/pages/ReportDetail/components/FlaggedValuesSection.tsx +++ b/frontend/src/pages/ReportDetail/components/FlaggedValuesSection.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { FC } from 'react'; import { useTranslation } from 'react-i18next'; import Icon from '../../../common/components/Icon/Icon'; import { LabValue } from '../../../common/models/medicalReport'; @@ -11,7 +11,7 @@ interface FlaggedValuesSectionProps { onToggle: () => void; } -const FlaggedValuesSection: React.FC = ({ +const FlaggedValuesSection: FC = ({ flaggedValues, isExpanded, onToggle, diff --git a/frontend/src/pages/ReportDetail/components/InfoCard.tsx b/frontend/src/pages/ReportDetail/components/InfoCard.tsx index 328b85bc..3ca4024f 100644 --- a/frontend/src/pages/ReportDetail/components/InfoCard.tsx +++ b/frontend/src/pages/ReportDetail/components/InfoCard.tsx @@ -1,8 +1,8 @@ -import React from 'react'; +import { FC } from 'react'; import './InfoCard.scss'; import bulbIcon from '../../../assets/icons/bulb.svg'; -const InfoCard: React.FC = () => { +const InfoCard: FC = () => { return (
diff --git a/frontend/src/pages/ReportDetail/components/LabValueItem.tsx b/frontend/src/pages/ReportDetail/components/LabValueItem.tsx index 0594a314..1f399998 100644 --- a/frontend/src/pages/ReportDetail/components/LabValueItem.tsx +++ b/frontend/src/pages/ReportDetail/components/LabValueItem.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { FC } from 'react'; import { useTranslation } from 'react-i18next'; import { LabValue } from '../../../common/models/medicalReport'; import classNames from 'classnames'; @@ -7,7 +7,7 @@ interface LabValueItemProps { item: LabValue; } -const LabValueItem: React.FC = ({ item }) => { +const LabValueItem: FC = ({ item }) => { const { t } = useTranslation(); // Parse suggestions into bullet points more intelligently diff --git a/frontend/src/pages/ReportDetail/components/LowConfidenceNotice.tsx b/frontend/src/pages/ReportDetail/components/LowConfidenceNotice.tsx index c7f5615c..399d4f36 100644 --- a/frontend/src/pages/ReportDetail/components/LowConfidenceNotice.tsx +++ b/frontend/src/pages/ReportDetail/components/LowConfidenceNotice.tsx @@ -1,11 +1,11 @@ -import React from 'react'; +import { FC } from 'react'; import { useTranslation } from 'react-i18next'; import info from '../../../assets/icons/info.svg'; /** * Component to display a notice when the confidence level is low */ -const LowConfidenceNotice: React.FC = () => { +const LowConfidenceNotice: FC = () => { const { t } = useTranslation(); return ( diff --git a/frontend/src/pages/ReportDetail/components/NormalValuesSection.tsx b/frontend/src/pages/ReportDetail/components/NormalValuesSection.tsx index 37c5d375..0aca58a0 100644 --- a/frontend/src/pages/ReportDetail/components/NormalValuesSection.tsx +++ b/frontend/src/pages/ReportDetail/components/NormalValuesSection.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { FC } from 'react'; import { useTranslation } from 'react-i18next'; import Icon from '../../../common/components/Icon/Icon'; import { LabValue } from '../../../common/models/medicalReport'; @@ -12,7 +12,7 @@ interface NormalValuesSectionProps { onToggle: () => void; } -const NormalValuesSection: React.FC = ({ +const NormalValuesSection: FC = ({ normalValues, isExpanded, onToggle, diff --git a/frontend/src/pages/ReportDetail/components/OriginalReportTab.tsx b/frontend/src/pages/ReportDetail/components/OriginalReportTab.tsx index d2888734..ecf8ad43 100644 --- a/frontend/src/pages/ReportDetail/components/OriginalReportTab.tsx +++ b/frontend/src/pages/ReportDetail/components/OriginalReportTab.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { FC } from 'react'; import { format } from 'date-fns'; import fileLinesIcon from '../../../assets/icons/file-lines.svg'; import { MedicalReport } from '../../../common/models/medicalReport'; @@ -9,7 +9,7 @@ interface OriginalReportTabProps { reportData: MedicalReport; } -const OriginalReportTab: React.FC = ({ reportData }) => { +const OriginalReportTab: FC = ({ reportData }) => { // Function to format file size in KB or MB const formatFileSize = (bytes: number): string => { if (bytes < 1024) return bytes + ' B'; diff --git a/frontend/src/pages/ReportDetail/components/ReportHeader.tsx b/frontend/src/pages/ReportDetail/components/ReportHeader.tsx index 79bee5d0..d9344fcb 100644 --- a/frontend/src/pages/ReportDetail/components/ReportHeader.tsx +++ b/frontend/src/pages/ReportDetail/components/ReportHeader.tsx @@ -1,31 +1,60 @@ -import React from 'react'; +import { FC } from 'react'; import { useTranslation } from 'react-i18next'; import closeIcon from '../../../assets/icons/close.svg'; +import backIcon from '../../../assets/icons/back.svg'; import bookmarkIcon from '../../../assets/icons/bookmark.svg'; import bookmarkFilledIcon from '../../../assets/icons/bookmark-filled.svg'; import { MedicalReport } from '../../../common/models/medicalReport'; +import { useHistory, useLocation } from 'react-router'; interface ReportHeaderProps { reportData: MedicalReport; - onClose: () => void; } -const ReportHeader: React.FC = ({ reportData, onClose }) => { +const ReportHeader: FC = ({ reportData }) => { const { t } = useTranslation(); + const location = useLocation<{ from?: string }>(); + const history = useHistory(); + + // Handle back button + const handleBack = () => { + history.goBack(); + }; + + // Handle close button + const handleClose = () => { + history.push('/tabs/home'); + }; return (
-
-

Results Analysis

- -
+ {/* Header component - only show if previous path was /tabs/processing */} + {location.state?.from === '/tabs/processing' && ( + <> +
+

Results Analysis

+ +
-
+
+ + )} {/* Category & Title */}
+ {/* Back button */} + {location.state?.from !== '/tabs/processing' && ( + <> + + + )} + {reportData.category && t(`list.${reportData.category}Category`, { ns: 'reportDetail' })}

{reportData.title}

diff --git a/frontend/src/pages/ReportDetail/components/ReportTabs.tsx b/frontend/src/pages/ReportDetail/components/ReportTabs.tsx index f1511b61..121cbf65 100644 --- a/frontend/src/pages/ReportDetail/components/ReportTabs.tsx +++ b/frontend/src/pages/ReportDetail/components/ReportTabs.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { FC } from 'react'; import aiInsightIcon from '../../../assets/icons/ai-insight.svg'; interface ReportTabsProps { @@ -6,7 +6,7 @@ interface ReportTabsProps { onTabChange: (tab: 'ai' | 'original') => void; } -const ReportTabs: React.FC = ({ activeTab, onTabChange }) => { +const ReportTabs: FC = ({ activeTab, onTabChange }) => { return (