From 3a18e17ece27b989fab64f784a6faa1c104aa1a4 Mon Sep 17 00:00:00 2001 From: aaldebs99 Date: Fri, 12 Dec 2025 00:36:42 +0000 Subject: [PATCH] feat(pwa): better PWA detection --- frontend/src/components/Layout/AppLayout.tsx | 18 ++++-- frontend/src/pages/DocumentsPage.tsx | 67 ++++++++++++++------ 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/Layout/AppLayout.tsx b/frontend/src/components/Layout/AppLayout.tsx index 03a2a55..310148a 100644 --- a/frontend/src/components/Layout/AppLayout.tsx +++ b/frontend/src/components/Layout/AppLayout.tsx @@ -46,6 +46,8 @@ import GlobalSearchBar from '../GlobalSearchBar'; import ThemeToggle from '../ThemeToggle/ThemeToggle'; import NotificationPanel from '../Notifications/NotificationPanel'; import LanguageSwitcher from '../LanguageSwitcher'; +import BottomNavigation from './BottomNavigation'; +import { usePWA } from '../../hooks/usePWA'; import { useTranslation } from 'react-i18next'; const drawerWidth = 280; @@ -80,6 +82,7 @@ const getNavigationItems = (t: (key: string) => string): NavigationItem[] => [ const AppLayout: React.FC = ({ children }) => { const theme = useMuiTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); + const isPWA = usePWA(); const [mobileOpen, setMobileOpen] = useState(false); const [anchorEl, setAnchorEl] = useState(null); const [notificationAnchorEl, setNotificationAnchorEl] = useState(null); @@ -660,16 +663,23 @@ const AppLayout: React.FC = ({ children }) => { }} > - + {children} {/* Notification Panel */} - + + {/* Bottom Navigation (PWA only) */} + ); }; diff --git a/frontend/src/pages/DocumentsPage.tsx b/frontend/src/pages/DocumentsPage.tsx index 66b64f2..afd7431 100644 --- a/frontend/src/pages/DocumentsPage.tsx +++ b/frontend/src/pages/DocumentsPage.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { @@ -108,10 +108,11 @@ const DocumentsPage: React.FC = () => { const [error, setError] = useState(null); const [viewMode, setViewMode] = useState('grid'); const [searchQuery, setSearchQuery] = useState(''); + const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(''); const [sortBy, setSortBy] = useState('created_at'); const [sortOrder, setSortOrder] = useState('desc'); const [ocrFilter, setOcrFilter] = useState(''); - + // Labels state const [availableLabels, setAvailableLabels] = useState([]); const [labelsLoading, setLabelsLoading] = useState(false); @@ -140,24 +141,57 @@ const DocumentsPage: React.FC = () => { const [retryHistoryModalOpen, setRetryHistoryModalOpen] = useState(false); const [selectedDocumentForHistory, setSelectedDocumentForHistory] = useState(null); + // Debounce search query to avoid making too many requests + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedSearchQuery(searchQuery); + // Reset to first page when search query changes + if (searchQuery !== debouncedSearchQuery) { + setPagination(prev => ({ ...prev, offset: 0 })); + } + }, 300); // 300ms debounce delay + + return () => clearTimeout(timer); + }, [searchQuery]); + useEffect(() => { fetchDocuments(); fetchLabels(); - }, [pagination?.limit, pagination?.offset, ocrFilter]); + }, [pagination?.limit, pagination?.offset, ocrFilter, debouncedSearchQuery]); const fetchDocuments = async (): Promise => { if (!pagination) return; - + try { setLoading(true); - const response = await documentService.listWithPagination( - pagination.limit, - pagination.offset, - ocrFilter || undefined - ); - // Backend returns wrapped object with documents and pagination - setDocuments(response.data.documents || []); - setPagination(response.data.pagination || { total: 0, limit: 20, offset: 0, has_more: false }); + + // If there's a search query, use the search API to search all documents + if (debouncedSearchQuery.trim()) { + const response = await documentService.enhancedSearch({ + query: debouncedSearchQuery.trim(), + limit: pagination.limit, + offset: pagination.offset, + include_snippets: false, + }); + + setDocuments(response.data.documents || []); + setPagination({ + total: response.data.total || 0, + limit: pagination.limit, + offset: pagination.offset, + has_more: (pagination.offset + pagination.limit) < (response.data.total || 0) + }); + } else { + // Otherwise, use normal pagination to list recent documents + const response = await documentService.listWithPagination( + pagination.limit, + pagination.offset, + ocrFilter || undefined + ); + // Backend returns wrapped object with documents and pagination + setDocuments(response.data.documents || []); + setPagination(response.data.pagination || { total: 0, limit: 20, offset: 0, has_more: false }); + } } catch (err) { setError(t('common.status.error')); console.error(err); @@ -263,12 +297,9 @@ const DocumentsPage: React.FC = () => { }); }; - const filteredDocuments = (documents || []).filter(doc => - doc.original_filename.toLowerCase().includes(searchQuery.toLowerCase()) || - doc.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())) - ); - - const sortedDocuments = [...filteredDocuments].sort((a, b) => { + // No need for client-side filtering anymore - search is done on the server + // When searchQuery is set, documents are already filtered by the server-side search API + const sortedDocuments = [...(documents || [])].sort((a, b) => { let aValue: any = a[sortBy]; let bValue: any = b[sortBy];