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 ThemeToggle from '../ThemeToggle/ThemeToggle';
|
||||||
import NotificationPanel from '../Notifications/NotificationPanel';
|
import NotificationPanel from '../Notifications/NotificationPanel';
|
||||||
import LanguageSwitcher from '../LanguageSwitcher';
|
import LanguageSwitcher from '../LanguageSwitcher';
|
||||||
|
import BottomNavigation from './BottomNavigation';
|
||||||
|
import { usePWA } from '../../hooks/usePWA';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const drawerWidth = 280;
|
const drawerWidth = 280;
|
||||||
|
|
@ -80,6 +82,7 @@ const getNavigationItems = (t: (key: string) => string): NavigationItem[] => [
|
||||||
const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
||||||
const theme = useMuiTheme();
|
const theme = useMuiTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
const isPWA = usePWA();
|
||||||
const [mobileOpen, setMobileOpen] = useState<boolean>(false);
|
const [mobileOpen, setMobileOpen] = useState<boolean>(false);
|
||||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
const [notificationAnchorEl, setNotificationAnchorEl] = useState<null | HTMLElement>(null);
|
const [notificationAnchorEl, setNotificationAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
|
|
@ -660,7 +663,11 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Toolbar />
|
<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}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
@ -670,6 +677,9 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
||||||
anchorEl={notificationAnchorEl}
|
anchorEl={notificationAnchorEl}
|
||||||
onClose={handleNotificationClose}
|
onClose={handleNotificationClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Bottom Navigation (PWA only) */}
|
||||||
|
<BottomNavigation />
|
||||||
</Box>
|
</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 { useNavigate } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
|
|
@ -108,6 +108,7 @@ const DocumentsPage: React.FC = () => {
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [viewMode, setViewMode] = useState<ViewMode>('grid');
|
const [viewMode, setViewMode] = useState<ViewMode>('grid');
|
||||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||||
|
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState<string>('');
|
||||||
const [sortBy, setSortBy] = useState<SortField>('created_at');
|
const [sortBy, setSortBy] = useState<SortField>('created_at');
|
||||||
const [sortOrder, setSortOrder] = useState<SortOrder>('desc');
|
const [sortOrder, setSortOrder] = useState<SortOrder>('desc');
|
||||||
const [ocrFilter, setOcrFilter] = useState<string>('');
|
const [ocrFilter, setOcrFilter] = useState<string>('');
|
||||||
|
|
@ -140,16 +141,48 @@ const DocumentsPage: React.FC = () => {
|
||||||
const [retryHistoryModalOpen, setRetryHistoryModalOpen] = useState<boolean>(false);
|
const [retryHistoryModalOpen, setRetryHistoryModalOpen] = useState<boolean>(false);
|
||||||
const [selectedDocumentForHistory, setSelectedDocumentForHistory] = useState<string | null>(null);
|
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(() => {
|
useEffect(() => {
|
||||||
fetchDocuments();
|
fetchDocuments();
|
||||||
fetchLabels();
|
fetchLabels();
|
||||||
}, [pagination?.limit, pagination?.offset, ocrFilter]);
|
}, [pagination?.limit, pagination?.offset, ocrFilter, debouncedSearchQuery]);
|
||||||
|
|
||||||
const fetchDocuments = async (): Promise<void> => {
|
const fetchDocuments = async (): Promise<void> => {
|
||||||
if (!pagination) return;
|
if (!pagination) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
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(
|
const response = await documentService.listWithPagination(
|
||||||
pagination.limit,
|
pagination.limit,
|
||||||
pagination.offset,
|
pagination.offset,
|
||||||
|
|
@ -158,6 +191,7 @@ const DocumentsPage: React.FC = () => {
|
||||||
// Backend returns wrapped object with documents and pagination
|
// Backend returns wrapped object with documents and pagination
|
||||||
setDocuments(response.data.documents || []);
|
setDocuments(response.data.documents || []);
|
||||||
setPagination(response.data.pagination || { total: 0, limit: 20, offset: 0, has_more: false });
|
setPagination(response.data.pagination || { total: 0, limit: 20, offset: 0, has_more: false });
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(t('common.status.error'));
|
setError(t('common.status.error'));
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
@ -263,12 +297,9 @@ const DocumentsPage: React.FC = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredDocuments = (documents || []).filter(doc =>
|
// No need for client-side filtering anymore - search is done on the server
|
||||||
doc.original_filename.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
// When searchQuery is set, documents are already filtered by the server-side search API
|
||||||
doc.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase()))
|
const sortedDocuments = [...(documents || [])].sort((a, b) => {
|
||||||
);
|
|
||||||
|
|
||||||
const sortedDocuments = [...filteredDocuments].sort((a, b) => {
|
|
||||||
let aValue: any = a[sortBy];
|
let aValue: any = a[sortBy];
|
||||||
let bValue: any = b[sortBy];
|
let bValue: any = b[sortBy];
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue