feat(pwa): better PWA detection
This commit is contained in:
parent
0df61038a8
commit
3a18e17ece
|
|
@ -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<AppLayoutProps> = ({ children }) => {
|
||||
const theme = useMuiTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const isPWA = usePWA();
|
||||
const [mobileOpen, setMobileOpen] = useState<boolean>(false);
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const [notificationAnchorEl, setNotificationAnchorEl] = useState<null | HTMLElement>(null);
|
||||
|
|
@ -660,7 +663,11 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
|||
}}
|
||||
>
|
||||
<Toolbar />
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Box sx={{
|
||||
p: 3,
|
||||
// Add bottom padding when bottom nav is visible (PWA mode on mobile)
|
||||
pb: isPWA && isMobile ? 'calc(64px + 24px + env(safe-area-inset-bottom, 0px))' : 3,
|
||||
}}>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
|
|
@ -670,6 +677,9 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
|||
anchorEl={notificationAnchorEl}
|
||||
onClose={handleNotificationClose}
|
||||
/>
|
||||
|
||||
{/* Bottom Navigation (PWA only) */}
|
||||
<BottomNavigation />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,6 +108,7 @@ const DocumentsPage: React.FC = () => {
|
|||
const [error, setError] = useState<string | null>(null);
|
||||
const [viewMode, setViewMode] = useState<ViewMode>('grid');
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState<string>('');
|
||||
const [sortBy, setSortBy] = useState<SortField>('created_at');
|
||||
const [sortOrder, setSortOrder] = useState<SortOrder>('desc');
|
||||
const [ocrFilter, setOcrFilter] = useState<string>('');
|
||||
|
|
@ -140,16 +141,48 @@ const DocumentsPage: React.FC = () => {
|
|||
const [retryHistoryModalOpen, setRetryHistoryModalOpen] = useState<boolean>(false);
|
||||
const [selectedDocumentForHistory, setSelectedDocumentForHistory] = useState<string | null>(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<void> => {
|
||||
if (!pagination) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// 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,
|
||||
|
|
@ -158,6 +191,7 @@ const DocumentsPage: React.FC = () => {
|
|||
// 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];
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue