feat: rewire routes to BullMQ and speed up documents page
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user