Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions frontend/src/assets/icons/back.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions frontend/src/common/hooks/useChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
},
});
Expand All @@ -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'] });
},
});

Expand Down
6 changes: 3 additions & 3 deletions frontend/src/pages/Auth/SignIn/api/useSignIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'] });
},
});
};
8 changes: 5 additions & 3 deletions frontend/src/pages/Processing/ProcessingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/pages/ReportDetail/ReportDetailPage.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
116 changes: 27 additions & 89 deletions frontend/src/pages/ReportDetail/ReportDetailPage.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<MedicalReport> => {
const response = await axios.get<MedicalReport>(
`${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<MedicalReport> => {
const response = await axios.get<MedicalReport>(
`${API_URL}/api/reports/${id}`,
await getAuthConfig(),
);
return response.data;
};

// Fetch report data using react-query
const { data, isLoading, error } = useQuery<MedicalReport>({
const {
data: reportData,
isLoading,
error,
} = useQuery<MedicalReport>({
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 <IonPage></IonPage>;
Expand All @@ -71,7 +75,7 @@ const ReportDetailPage: React.FC = () => {
);
}

if (!data) {
if (!reportData) {
return (
<IonPage>
<IonContent className="ion-padding">
Expand All @@ -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 (
<IonPage className="report-detail-page">
<IonContent fullscreen>
{/* Header component */}
<ReportHeader reportData={reportData} onClose={handleClose} />
<ReportHeader reportData={reportData} />

{/* Tab selector for AI Insights vs Original Report */}
<ReportTabs activeTab={activeTab} onTabChange={handleTabChange} />
Expand All @@ -172,9 +110,9 @@ const ReportDetailPage: React.FC = () => {

{/* Action buttons at the bottom */}
<ActionButtons
onDiscard={handleDiscard}
onNewUpload={handleNewUpload}
reportId={reportId}
reportTitle={reportData.title}
setIsUploadModalOpen={setIsUploadModalOpen}
/>

<UploadModal
Expand Down
80 changes: 72 additions & 8 deletions frontend/src/pages/ReportDetail/components/ActionButtons.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import React, { useState } from 'react';
import { FC, useState } from 'react';
import { useTranslation } from 'react-i18next';
import ConfirmationModal from '../../../common/components/Modal/ConfirmationModal';
import { useToasts } from 'common/hooks/useToasts';
import { useQueryClient } from '@tanstack/react-query';
import { useHistory } from 'react-router';
import axios from 'axios';
import { QueryKey } from 'common/utils/constants';
import { getAuthConfig } from 'common/api/reportService';

const API_URL = import.meta.env.VITE_BASE_URL_API || '';

interface ActionButtonsProps {
onDiscard: (setIsProcessing: (isProcessing: boolean) => void) => Promise<void>;
onNewUpload: (setIsProcessing: (isProcessing: boolean) => void) => Promise<void>;
reportTitle?: string;
reportId?: string;
reportId: string;
reportTitle: string;
setIsUploadModalOpen: (isOpen: boolean) => void;
}

const ActionButtons: React.FC<ActionButtonsProps> = ({ onDiscard, onNewUpload, reportTitle }) => {
const ActionButtons: FC<ActionButtonsProps> = ({ 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);
Expand All @@ -22,7 +32,7 @@ const ActionButtons: React.FC<ActionButtonsProps> = ({ onDiscard, onNewUpload, r
const handleConfirmDiscard = async () => {
setShowConfirmDiscard(false);

await onDiscard(setIsProcessing);
await handleDiscard();
};

const handleCancelDiscard = () => {
Expand All @@ -36,13 +46,67 @@ const ActionButtons: React.FC<ActionButtonsProps> = ({ 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 (
<>
<div className="report-detail-page__actions">
Expand Down
11 changes: 4 additions & 7 deletions frontend/src/pages/ReportDetail/components/AiAnalysisTab.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -10,13 +10,10 @@ interface AiAnalysisTabProps {
isEmergencyAlertVisible?: boolean;
}

const AiAnalysisTab: React.FC<AiAnalysisTabProps> = ({
reportData,
isEmergencyAlertVisible = true,
}) => {
const AiAnalysisTab: FC<AiAnalysisTabProps> = ({ 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);
Expand Down
Loading