feat: rewire routes to BullMQ and speed up documents page

This commit is contained in:
ff
2026-04-13 20:43:21 -04:00
parent 4e981c644f
commit 05a8a220bd
5 changed files with 171 additions and 526 deletions

View File

@@ -27,8 +27,6 @@ import { getPageNumbers } from "@/utils/pageNumberGenerator";
import {
getPatientDocuments,
deleteDocument,
viewDocument,
downloadDocument,
formatFileSize,
type PatientDocument
} from "@/lib/api/documents";
@@ -38,7 +36,6 @@ export default function DocumentsPage() {
const [expandedGroupId, setExpandedGroupId] = useState<number | null>(null);
// pagination state for the expanded group
// pagination state
const [currentPage, setCurrentPage] = useState<number>(1);
const [limit, setLimit] = useState<number>(5);
const offset = (currentPage - 1) * limit;
@@ -46,11 +43,7 @@ export default function DocumentsPage() {
number | null
>(null);
// Patient documents state
const [patientDocuments, setPatientDocuments] = useState<PatientDocument[]>([]);
const [patientDocumentsLoading, setPatientDocumentsLoading] = useState(false);
const [showPatientDocuments, setShowPatientDocuments] = useState(false);
const [documentThumbnails, setDocumentThumbnails] = useState<{ [key: number]: string }>({});
// Document preview state
const [previewDocumentId, setPreviewDocumentId] = useState<number | null>(null);
@@ -72,96 +65,50 @@ export default function DocumentsPage() {
setLimit(5);
setCurrentPage(1);
setTotalForExpandedGroup(null);
setShowPatientDocuments(false); // Reset documents toggle
// close the preview modal
setShowPatientDocuments(false);
setIsPreviewModalOpen(false);
setPreviewDocumentId(null);
// Load patient documents when patient is selected
if (selectedPatient?.id) {
console.log("Patient selected, loading documents for:", selectedPatient.id);
loadPatientDocuments(selectedPatient.id);
} else {
console.log("No patient selected, clearing documents");
setPatientDocuments([]);
}
}, [selectedPatient]);
// Load patient documents function
const loadPatientDocuments = async (patientId: number) => {
try {
setPatientDocumentsLoading(true);
console.log("Loading documents for patient:", patientId);
const response = await getPatientDocuments(patientId);
console.log("Loaded documents:", response);
if (response.success) {
setPatientDocuments(response.documents);
// Load thumbnails for image documents
loadDocumentThumbnails(response.documents);
} else {
throw new Error("Failed to load documents");
}
} catch (error) {
console.error("Failed to load patient documents:", error);
toast({
title: "Error",
description: "Failed to load patient documents",
variant: "destructive",
});
} finally {
setPatientDocumentsLoading(false);
// Patient documents — React Query for caching (re-selecting same patient shows instantly)
const { data: patientDocuments = [], isLoading: patientDocumentsLoading } =
useQuery<PatientDocument[]>({
queryKey: ["patientDocuments", selectedPatient?.id],
enabled: !!selectedPatient?.id,
staleTime: 2 * 60 * 1000,
queryFn: async () => {
const response = await getPatientDocuments(selectedPatient!.id);
if (!response.success) throw new Error("Failed to load documents");
return response.documents;
},
});
// Derive thumbnails synchronously — no extra async round-trip
const documentThumbnails = useMemo(() => {
const result: { [key: number]: string } = {};
for (const doc of patientDocuments) {
if (doc.mimeType.startsWith("image/")) result[doc.id] = doc.filePath;
}
};
return result;
}, [patientDocuments]);
// Load thumbnails for image documents
const loadDocumentThumbnails = async (documents: PatientDocument[]) => {
const thumbnails: { [key: number]: string } = {};
for (const document of documents) {
if (document.mimeType.startsWith('image/')) {
try {
// Use the document's filePath as the thumbnail URL
thumbnails[document.id] = document.filePath;
} catch (error) {
console.error(`Failed to load thumbnail for document ${document.id}:`, error);
}
}
}
setDocumentThumbnails(thumbnails);
};
// Refresh patient documents (for after upload)
const refreshPatientDocuments = async () => {
if (selectedPatient?.id) {
await loadPatientDocuments(selectedPatient.id);
}
};
// Listen for document upload events
// Listen for document upload events — invalidate React Query cache instead of manual refetch
useEffect(() => {
const handleDocumentUpload = (event: CustomEvent) => {
console.log('Document upload event received:', event.detail);
refreshPatientDocuments();
};
// Add event listener for document uploads
window.addEventListener('documentUploaded', handleDocumentUpload as EventListener);
// Also listen for storage events (for cross-tab communication)
const handleStorageChange = (e: StorageEvent) => {
if (e.key === 'documentUploaded' && e.newValue) {
refreshPatientDocuments();
const refresh = () => {
if (selectedPatient?.id) {
queryClient.invalidateQueries({ queryKey: ["patientDocuments", selectedPatient.id] });
}
};
window.addEventListener('storage', handleStorageChange);
window.addEventListener("documentUploaded", refresh);
const handleStorageChange = (e: StorageEvent) => {
if (e.key === "documentUploaded" && e.newValue) refresh();
};
window.addEventListener("storage", handleStorageChange);
// Cleanup listeners
return () => {
window.removeEventListener('documentUploaded', handleDocumentUpload as EventListener);
window.removeEventListener('storage', handleStorageChange);
window.removeEventListener("documentUploaded", refresh);
window.removeEventListener("storage", handleStorageChange);
};
}, [selectedPatient]);
@@ -213,6 +160,7 @@ export default function DocumentsPage() {
const { data: groups = [], isLoading: isLoadingGroups } = useQuery({
queryKey: ["groups", selectedPatient?.id],
enabled: !!selectedPatient,
staleTime: 2 * 60 * 1000,
queryFn: async () => {
const res = await apiRequest(
"GET",
@@ -380,9 +328,9 @@ export default function DocumentsPage() {
{/* Existing Groups Section */}
<div>
{/* <h4 className="text-lg font-semibold mb-3">Document Groups</h4> */}
{isLoadingGroups || patientDocumentsLoading ? (
{isLoadingGroups ? (
<div>Loading groups</div>
) : (groups as any[]).length === 0 && patientDocuments.length === 0 ? (
) : (groups as any[]).length === 0 && patientDocuments.length === 0 && !patientDocumentsLoading ? (
<div className="text-sm text-muted-foreground">
No groups found.
</div>