Skip to content

Commit ae28268

Browse files
authored
Merge pull request #334 from CodeForPhilly/display_source_08102025
Display source 08102025
2 parents 17bd0f6 + 0add944 commit ae28268

File tree

12 files changed

+1916
-1463
lines changed

12 files changed

+1916
-1463
lines changed

frontend/src/api/apiClient.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,18 @@ const handleRuleExtraction = async (guid: string) => {
6767
}
6868
};
6969

70+
const fetchRiskDataWithSources = async (medication: string, source: "include" | "diagnosis" = "include") => {
71+
try {
72+
const response = await api.post(`/v1/api/riskWithSources`, {
73+
drug: medication,
74+
source: source,
75+
});
76+
return response.data;
77+
} catch (error) {
78+
console.error("Error fetching risk data: ", error);
79+
throw error;
80+
}
81+
};
7082

7183
interface StreamCallbacks {
7284
onContent?: (content: string) => void;
@@ -165,7 +177,6 @@ const handleSendDrugSummaryStream = async (
165177
}
166178
};
167179

168-
169180
// Legacy function for backward compatibility
170181
const handleSendDrugSummaryStreamLegacy = async (
171182
message: string,
@@ -256,7 +267,6 @@ const updateConversationTitle = async (
256267
}
257268
};
258269

259-
260270
export {
261271
handleSubmitFeedback,
262272
handleSendDrugSummary,
@@ -268,5 +278,6 @@ export {
268278
deleteConversation,
269279
updateConversationTitle,
270280
handleSendDrugSummaryStream,
271-
handleSendDrugSummaryStreamLegacy
281+
handleSendDrugSummaryStreamLegacy,
282+
fetchRiskDataWithSources
272283
};

frontend/src/pages/DrugSummary/PDFViewer.tsx

Lines changed: 78 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ interface DocumentLoadSuccess {
1111
numPages: number;
1212
}
1313

14+
const PAGE_INIT_DELAY = 800;
15+
1416
const PDFViewer = () => {
1517
const [numPages, setNumPages] = useState<number | null>(null);
1618
const [pageNumber, setPageNumber] = useState(1);
@@ -24,81 +26,96 @@ const PDFViewer = () => {
2426
null
2527
);
2628

27-
const manualScrollInProgress = useRef(false);
28-
const PAGE_INIT_DELAY = 800;
29-
3029
const headerRef = useRef<HTMLDivElement>(null);
3130
const containerRef = useRef<HTMLDivElement>(null);
3231
const contentRef = useRef<HTMLDivElement>(null);
3332
const pageRefs = useRef<Record<number, HTMLDivElement | null>>({});
34-
const initializationRef = useRef(false);
33+
const prevGuidRef = useRef<string | null>(null);
34+
const isFetchingRef = useRef(false);
3535

3636
const location = useLocation();
3737
const navigate = useNavigate();
3838
const params = new URLSearchParams(location.search);
3939
const guid = params.get("guid");
4040
const pageParam = params.get("page");
4141

42-
const baseURL = import.meta.env.VITE_API_BASE_URL;
43-
const pdfUrl = useMemo(
44-
() => (guid ? `${baseURL}/v1/api/uploadFile/${guid}` : null),
45-
[guid, baseURL]
46-
);
42+
const baseURL = import.meta.env.VITE_API_BASE_URL as string | undefined;
43+
44+
const pdfUrl = useMemo(() => {
45+
const url = guid && baseURL ? `${baseURL}/v1/api/uploadFile/${guid}` : null;
46+
47+
return url;
48+
}, [guid, baseURL]);
4749

4850
useEffect(() => {
49-
pageRefs.current = {};
50-
setIsDocumentLoaded(false);
51-
initializationRef.current = false;
51+
const nextPage = pageParam ? parseInt(pageParam, 10) : 1;
52+
const guidChanged = guid !== prevGuidRef.current;
53+
54+
if (guidChanged) {
55+
pageRefs.current = {};
56+
setIsDocumentLoaded(false);
57+
setNumPages(null);
58+
setPdfData(null);
59+
setPageNumber(1);
60+
}
5261

53-
if (pageParam) {
54-
const page = parseInt(pageParam, 10);
55-
if (!isNaN(page) && page > 0) setTargetPageAfterLoad(page);
62+
if (!isNaN(nextPage) && nextPage > 0) {
63+
setTargetPageAfterLoad(nextPage);
5664
} else {
5765
setTargetPageAfterLoad(1);
5866
}
59-
}, [guid, pageParam]);
67+
68+
prevGuidRef.current = guid;
69+
}, [guid, pageParam, location.pathname, location.search]);
6070

6171
const scrollToPage = useCallback(
6272
(page: number) => {
63-
if (page < 1 || !numPages || page > numPages) return;
73+
if (!numPages || page < 1 || page > numPages) {
74+
return;
75+
}
76+
6477
const targetRef = pageRefs.current[page];
65-
if (!targetRef) return;
66-
67-
manualScrollInProgress.current = true;
68-
targetRef.scrollIntoView({ behavior: "smooth", block: "start" });
69-
70-
const observer = new IntersectionObserver(
71-
(entries, obs) => {
72-
const entry = entries[0];
73-
if (entry?.isIntersecting) {
74-
manualScrollInProgress.current = false;
75-
obs.disconnect();
76-
}
77-
},
78-
{ threshold: 0.5 }
79-
);
80-
observer.observe(targetRef);
78+
if (!targetRef) {
79+
setTimeout(() => scrollToPage(page), 100);
80+
return;
81+
}
8182

82-
const newParams = new URLSearchParams(location.search);
83-
newParams.set("page", String(page));
84-
navigate(`${location.pathname}?${newParams.toString()}`, {
85-
replace: true,
83+
targetRef.scrollIntoView({
84+
behavior: "smooth",
85+
block: "start",
86+
inline: "nearest",
8687
});
88+
89+
const newParams = new URLSearchParams(location.search);
90+
const oldPage = newParams.get("page");
91+
if (oldPage !== String(page)) {
92+
newParams.set("page", String(page));
93+
const newUrl = `${location.pathname}?${newParams.toString()}`;
94+
navigate(newUrl, { replace: true });
95+
}
96+
8797
setPageNumber(page);
8898
},
89-
[numPages, navigate, location.pathname, location.search]
99+
[numPages, location.pathname, location.search, navigate, pageNumber]
90100
);
91101

102+
// Preload-aware navigation: if not loaded yet, just remember target page.
92103
const goToPage = useCallback(
93104
(page: number) => {
94105
if (typeof page !== "number" || isNaN(page)) return;
95-
if (page < 1) page = 1;
96-
else if (numPages && page > numPages) page = numPages;
97106

98-
setPageNumber(page);
99-
scrollToPage(page);
107+
const clamped = Math.max(1, numPages ? Math.min(page, numPages) : page);
108+
109+
if (!isDocumentLoaded || !numPages) {
110+
setTargetPageAfterLoad(clamped);
111+
return;
112+
}
113+
114+
if (clamped === pageNumber) return;
115+
setPageNumber(clamped);
116+
scrollToPage(clamped);
100117
},
101-
[numPages, scrollToPage]
118+
[isDocumentLoaded, numPages, pageNumber, scrollToPage]
102119
);
103120

104121
useEffect(() => {
@@ -115,21 +132,16 @@ const PDFViewer = () => {
115132
}, [goToPage]);
116133

117134
useEffect(() => {
118-
if (
119-
isDocumentLoaded &&
120-
numPages &&
121-
targetPageAfterLoad &&
122-
Object.keys(pageRefs.current).length > 0
123-
) {
135+
if (isDocumentLoaded && numPages && targetPageAfterLoad) {
124136
const validPage = Math.min(Math.max(1, targetPageAfterLoad), numPages);
125137
setPageNumber(validPage);
126138

127-
const timeoutId = setTimeout(() => {
139+
const timer = setTimeout(() => {
128140
scrollToPage(validPage);
129141
setTargetPageAfterLoad(null);
130142
}, PAGE_INIT_DELAY);
131143

132-
return () => clearTimeout(timeoutId);
144+
return () => clearTimeout(timer);
133145
}
134146
}, [isDocumentLoaded, numPages, targetPageAfterLoad, scrollToPage]);
135147

@@ -169,7 +181,13 @@ const PDFViewer = () => {
169181

170182
const fetchPdf = useCallback(async () => {
171183
if (!pdfUrl) return;
184+
if (isFetchingRef.current) {
185+
console.log("⏳ fetchPdf already in progress, skipping duplicate call");
186+
return;
187+
}
188+
172189
try {
190+
isFetchingRef.current = true;
173191
setLoading(true);
174192
setError(null);
175193
const token = localStorage.getItem("access");
@@ -193,6 +211,7 @@ const PDFViewer = () => {
193211
setPdfData(null);
194212
} finally {
195213
setLoading(false);
214+
isFetchingRef.current = false;
196215
}
197216
}, [pdfUrl, isPDF]);
198217

@@ -230,13 +249,11 @@ const PDFViewer = () => {
230249
231250
</button>
232251
<span className="text-sm">
233-
Page {pageNumber} of {numPages || "-"}
252+
Page {pageNumber} of {numPages ?? "-"}
234253
</span>
235254
<button
236-
onClick={() =>
237-
goToPage(Math.min(pageNumber + 1, numPages || pageNumber))
238-
}
239-
disabled={!numPages || pageNumber >= numPages}
255+
onClick={() => goToPage(pageNumber + 1)}
256+
disabled={!numPages || pageNumber >= (numPages ?? 1)}
240257
className="px-3 py-1 bg-white border rounded"
241258
>
242259
@@ -282,6 +299,7 @@ const PDFViewer = () => {
282299
style={{ maxHeight: containerSize.height }}
283300
>
284301
<Document
302+
key={guid ?? "doc"}
285303
file={file}
286304
onLoadSuccess={onDocumentLoadSuccess}
287305
onLoadError={(err) => setError(err.message)}
@@ -294,18 +312,17 @@ const PDFViewer = () => {
294312
<div
295313
key={pageNum}
296314
ref={(el) => {
297-
if (el) pageRefs.current[pageNum] = el;
315+
pageRefs.current[pageNum] = el;
298316
}}
299317
className="mb-4 w-full"
318+
data-page={pageNum}
300319
>
301320
<Page
302321
pageNumber={pageNum}
303-
scale={scale}
322+
width={Math.max(0, (containerSize.width || 0) - 50)}
304323
renderTextLayer={true}
305-
renderAnnotationLayer={true}
324+
renderAnnotationLayer={false}
306325
className="shadow-lg"
307-
height={containerSize.height || undefined}
308-
width={(containerSize.width || 0) - 50}
309326
/>
310327
<div className="text-center text-gray-500 text-sm mt-1">
311328
Page {pageNum} of {numPages}

0 commit comments

Comments
 (0)