import { useState, useEffect, useRef, useCallback } from 'react'; import { Box, TextField, Typography, Paper, List, ListItem, ListItemButton, ListItemText, CircularProgress, InputAdornment, Divider, } from '@mui/material'; import { Search as SearchIcon } from '@mui/icons-material'; import { useQuery } from '@tanstack/react-query'; import DOMPurify from 'dompurify'; import DashboardLayout from '../components/dashboard/DashboardLayout'; import { bookstackApi } from '../services/bookstack'; import type { BookStackPage, BookStackSearchResult } from '../types/bookstack.types'; export default function Wissen() { const [searchTerm, setSearchTerm] = useState(''); const [debouncedSearch, setDebouncedSearch] = useState(''); const [selectedPageId, setSelectedPageId] = useState(null); const debounceRef = useRef | null>(null); useEffect(() => { if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => { setDebouncedSearch(searchTerm.trim()); }, 400); return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; }, [searchTerm]); const recentQuery = useQuery({ queryKey: ['bookstack', 'recent'], queryFn: () => bookstackApi.getRecent(), }); const searchQuery = useQuery({ queryKey: ['bookstack', 'search', debouncedSearch], queryFn: () => bookstackApi.search(debouncedSearch), enabled: debouncedSearch.length > 0, }); const pageQuery = useQuery({ queryKey: ['bookstack', 'page', selectedPageId], queryFn: () => bookstackApi.getPage(selectedPageId!), enabled: selectedPageId !== null, }); const handleSelectPage = useCallback((id: number) => { setSelectedPageId(id); }, []); const isNotConfigured = recentQuery.data && !recentQuery.data.configured; if (isNotConfigured) { return ( Wissen BookStack ist nicht konfiguriert. Bitte BOOKSTACK_URL, BOOKSTACK_TOKEN_ID und BOOKSTACK_TOKEN_SECRET in der .env-Datei setzen. ); } const isSearching = debouncedSearch.length > 0; const listItems: (BookStackSearchResult | BookStackPage)[] = isSearching ? searchQuery.data?.data ?? [] : recentQuery.data?.data ?? []; const listLoading = isSearching ? searchQuery.isLoading : recentQuery.isLoading; return ( {/* Left panel: search + list */} setSearchTerm(e.target.value)} InputProps={{ startAdornment: ( ), }} /> {listLoading ? ( ) : listItems.length === 0 ? ( {isSearching ? 'Keine Ergebnisse gefunden.' : 'Keine Seiten vorhanden.'} ) : ( {listItems.map((item) => ( handleSelectPage(item.id)} > ))} )} {/* Right panel: page content */} {!selectedPageId ? ( Seite aus der Liste auswaehlen, um den Inhalt anzuzeigen. ) : pageQuery.isLoading ? ( ) : pageQuery.isError ? ( Fehler beim Laden der Seite. ) : pageQuery.data?.data ? ( {pageQuery.data.data.name} {pageQuery.data.data.book && ( Buch: {pageQuery.data.data.book.name} )} ({ '& h1, & h2, & h3, & h4, & h5, & h6': { color: theme.palette.text.primary, mt: 2, mb: 1, }, '& p': { color: theme.palette.text.primary, lineHeight: 1.7, mb: 1, }, '& a': { color: theme.palette.primary.main, textDecoration: 'none', '&:hover': { textDecoration: 'underline' }, }, '& table': { borderCollapse: 'collapse', width: '100%', mb: 2, }, '& th, & td': { border: `1px solid ${theme.palette.divider}`, padding: theme.spacing(1), textAlign: 'left', }, '& th': { backgroundColor: theme.palette.action.hover, fontWeight: 600, }, '& img': { maxWidth: '100%', height: 'auto', borderRadius: 1, }, '& code': { backgroundColor: theme.palette.action.hover, padding: '2px 6px', borderRadius: 1, fontSize: '0.875em', }, '& pre': { backgroundColor: theme.palette.action.hover, padding: theme.spacing(2), borderRadius: 1, overflow: 'auto', }, '& ul, & ol': { pl: 3, mb: 1, }, '& blockquote': { borderLeft: `4px solid ${theme.palette.primary.main}`, pl: 2, ml: 0, color: theme.palette.text.secondary, }, })} dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(pageQuery.data.data.html), }} /> ) : ( Seite nicht gefunden. )} ); }