fix bookstack display error

This commit is contained in:
Matthias Hochmeister
2026-03-04 07:52:11 +01:00
parent 32473f8329
commit 11335748c2
3 changed files with 81 additions and 13 deletions

View File

@@ -78,7 +78,23 @@ const BookStackRecentWidget: React.FC = () => {
const configured = data?.configured ?? true; const configured = data?.configured ?? true;
const pages = data?.data ?? []; const pages = data?.data ?? [];
if (!configured) return null; if (!configured) {
return (
<Card sx={{ height: '100%' }}>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<MenuBook color="disabled" />
<Typography variant="h6" sx={{ flexGrow: 1 }} color="text.secondary">
BookStack Neueste Seiten
</Typography>
</Box>
<Typography variant="body2" color="text.secondary" sx={{ py: 2, textAlign: 'center' }}>
BookStack nicht eingerichtet
</Typography>
</CardContent>
</Card>
);
}
return ( return (
<Card <Card

View File

@@ -8,8 +8,10 @@ import {
Divider, Divider,
CircularProgress, CircularProgress,
InputAdornment, InputAdornment,
Skeleton,
} from '@mui/material'; } from '@mui/material';
import { Search, MenuBook } from '@mui/icons-material'; import { Search, MenuBook } from '@mui/icons-material';
import { useQuery } from '@tanstack/react-query';
import { bookstackApi } from '../../services/bookstack'; import { bookstackApi } from '../../services/bookstack';
import type { BookStackSearchResult } from '../../types/bookstack.types'; import type { BookStackSearchResult } from '../../types/bookstack.types';
@@ -63,29 +65,47 @@ const ResultRow: React.FC<{ result: BookStackSearchResult; showDivider: boolean
const BookStackSearchWidget: React.FC = () => { const BookStackSearchWidget: React.FC = () => {
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const [results, setResults] = useState<BookStackSearchResult[]>([]); const [results, setResults] = useState<BookStackSearchResult[]>([]);
const [loading, setLoading] = useState(false); const [searching, setSearching] = useState(false);
const [configured, setConfigured] = useState(true);
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null); const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const latestQueryRef = useRef<string>('');
const { data, isLoading: configLoading } = useQuery({
queryKey: ['bookstack-recent'],
queryFn: () => bookstackApi.getRecent(),
refetchInterval: 5 * 60 * 1000,
retry: 1,
});
// undefined while loading, true/false once known
const configured = configLoading ? undefined : (data?.configured ?? false);
useEffect(() => { useEffect(() => {
if (debounceRef.current) clearTimeout(debounceRef.current); if (debounceRef.current) clearTimeout(debounceRef.current);
if (!query.trim()) { if (!query.trim()) {
setResults([]); setResults([]);
setLoading(false); setSearching(false);
return; return;
} }
setLoading(true); setSearching(true);
const thisQuery = query.trim();
latestQueryRef.current = thisQuery;
debounceRef.current = setTimeout(async () => { debounceRef.current = setTimeout(async () => {
try { try {
const response = await bookstackApi.search(query.trim()); const response = await bookstackApi.search(thisQuery);
setConfigured(response.configured); if (latestQueryRef.current === thisQuery) {
setResults(response.data); setResults(response.data);
}
} catch { } catch {
if (latestQueryRef.current === thisQuery) {
setResults([]); setResults([]);
}
} finally { } finally {
setLoading(false); if (latestQueryRef.current === thisQuery) {
setSearching(false);
}
} }
}, 400); }, 400);
@@ -94,7 +114,37 @@ const BookStackSearchWidget: React.FC = () => {
}; };
}, [query]); }, [query]);
if (!configured) return null; if (configured === undefined) {
return (
<Card sx={{ height: '100%' }}>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<MenuBook color="primary" />
<Skeleton variant="text" width={160} height={32} />
</Box>
<Skeleton variant="rectangular" height={40} sx={{ borderRadius: 1 }} />
</CardContent>
</Card>
);
}
if (configured === false) {
return (
<Card sx={{ height: '100%' }}>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<MenuBook color="disabled" />
<Typography variant="h6" sx={{ flexGrow: 1 }} color="text.secondary">
BookStack Suche
</Typography>
</Box>
<Typography variant="body2" color="text.secondary" sx={{ py: 2, textAlign: 'center' }}>
BookStack nicht eingerichtet
</Typography>
</CardContent>
</Card>
);
}
return ( return (
<Card <Card
@@ -121,13 +171,13 @@ const BookStackSearchWidget: React.FC = () => {
InputProps={{ InputProps={{
startAdornment: ( startAdornment: (
<InputAdornment position="start"> <InputAdornment position="start">
{loading ? <CircularProgress size={16} /> : <Search fontSize="small" />} {searching ? <CircularProgress size={16} /> : <Search fontSize="small" />}
</InputAdornment> </InputAdornment>
), ),
}} }}
/> />
{!loading && query.trim() && results.length === 0 && ( {!searching && query.trim() && results.length === 0 && (
<Typography variant="body2" color="text.secondary" sx={{ py: 2, textAlign: 'center' }}> <Typography variant="body2" color="text.secondary" sx={{ py: 2, textAlign: 'center' }}>
Keine Ergebnisse für {query}" Keine Ergebnisse für {query}"
</Typography> </Typography>

View File

@@ -4,3 +4,5 @@ export { default as StatsCard } from './StatsCard';
export { default as DashboardLayout } from './DashboardLayout'; export { default as DashboardLayout } from './DashboardLayout';
export { default as PersonalWarningsBanner } from './PersonalWarningsBanner'; export { default as PersonalWarningsBanner } from './PersonalWarningsBanner';
export { default as UpcomingEventsWidget } from './UpcomingEventsWidget'; export { default as UpcomingEventsWidget } from './UpcomingEventsWidget';
export { default as BookStackRecentWidget } from './BookStackRecentWidget';
export { default as BookStackSearchWidget } from './BookStackSearchWidget';