diff --git a/project/frontend/src/components/KennzahlenTable.tsx b/project/frontend/src/components/KennzahlenTable.tsx index c78bfbb..722df56 100644 --- a/project/frontend/src/components/KennzahlenTable.tsx +++ b/project/frontend/src/components/KennzahlenTable.tsx @@ -1,145 +1,130 @@ import { - Table, TableBody, TableCell, TableContainer, - TableHead, TableRow, Paper, Box, - Dialog, DialogActions, DialogContent, DialogTitle, - TextField, Button, Link - } from '@mui/material'; - import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; - import SearchIcon from '@mui/icons-material/Search'; - import EditIcon from '@mui/icons-material/Edit'; - import { useState } from 'react'; - - - // Beispiel-Daten - const exampleData = [ - { label: 'Fondsname', value: 'Fund Real Estate Prime Europe', page: 1, status: 'ok' }, - { label: 'Fondsmanager', value: '', page: 1, status: 'error' }, - { label: 'Risikoprofil', value: 'Core/Core+', page: 10, status: 'warning' }, - { label: 'LTV', value: '30-35 %', page: 8, status: 'ok' }, - { label: 'Ausschüttungsrendite', value: '4%', page: 34, status: 'ok' } - ]; - - interface KennzahlenTableProps { - onPageClick?: (page: number) => void; - } - - // React-Komponente - export default function KennzahlenTable({ onPageClick }: KennzahlenTableProps) { - // Zustand für bearbeitbare Daten - const [rows, setRows] = useState(exampleData); - - // Zustände für Dialog-Funktion - const [open, setOpen] = useState(false); // Dialog anzeigen? - const [currentValue, setCurrentValue] = useState(''); // Eingabewert - const [currentIndex, setCurrentIndex] = useState(null); // Zeilenindex - - // Beim Klick auf das Stift-Icon: Dialog öffnen - const handleEditClick = (value: string, index: number) => { - setCurrentValue(value); - setCurrentIndex(index); - setOpen(true); - }; - - // Wert speichern und Dialog schließen - const handleSave = () => { - if (currentIndex !== null) { - const updated = [...rows]; - updated[currentIndex].value = currentValue; - setRows(updated); - } - setOpen(false); - }; - - return ( - <> - - - {/* Tabellenkopf */} - - - Kennzahl - Wert - Seite - - - - {/* Tabelleninhalt */} - - {rows.map((row, index) => { - // Rahmenfarbe anhand Status - let borderColor = 'transparent'; - if (row.status === 'error') borderColor = 'red'; - else if (row.status === 'warning') borderColor = '#f6ed48'; - - return ( - - {/* Kennzahl */} - {row.label} - - {/* Wert mit Status-Icons + Stift rechts */} - - - - {row.status === 'error' && } - {row.status === 'warning' && } - {row.value || '—'} - - - {/* Stift-Icon */} - handleEditClick(row.value, index)} - /> + Table, TableBody, TableCell, TableContainer, + TableHead, TableRow, Paper, Box, + Dialog, DialogActions, DialogContent, DialogTitle, + TextField, Button, Link +} from '@mui/material'; +import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; +import SearchIcon from '@mui/icons-material/Search'; +import EditIcon from '@mui/icons-material/Edit'; +import { useState } from 'react'; + +const exampleData = [ + { label: 'Fondsname', value: 'Fund Real Estate Prime Europe', page: 1, status: 'ok' }, + { label: 'Fondsmanager', value: '', page: 1, status: 'error' }, + { label: 'Risikoprofil', value: 'Core/Core+', page: 10, status: 'warning' }, + { label: 'LTV', value: '30-35 %', page: 8, status: 'ok' }, + { label: 'Ausschüttungsrendite', value: '4%', page: 34, status: 'ok' } +]; + +interface KennzahlenTableProps { + onPageClick?: (page: number) => void; + setSelectedLabel?: (label: string) => void; +} + +export default function KennzahlenTable({ onPageClick, setSelectedLabel }: KennzahlenTableProps) { + const [rows, setRows] = useState(exampleData); + const [open, setOpen] = useState(false); + const [currentValue, setCurrentValue] = useState(''); + const [currentIndex, setCurrentIndex] = useState(null); + + const handleEditClick = (value: string, index: number) => { + setCurrentValue(value); + setCurrentIndex(index); + setOpen(true); + }; + + const handleSave = () => { + if (currentIndex !== null) { + const updated = [...rows]; + updated[currentIndex].value = currentValue; + setRows(updated); + } + setOpen(false); + }; + + return ( + <> + +
+ + + Kennzahl + Wert + Seite + + + + {rows.map((row, index) => { + let borderColor = 'transparent'; + if (row.status === 'error') borderColor = 'red'; + else if (row.status === 'warning') borderColor = '#f6ed48'; + + return ( + setSelectedLabel?.(row.label)} + hover + sx={{ cursor: 'pointer' }} + > + {row.label} + + + + {row.status === 'error' && } + {row.status === 'warning' && } + {row.value || '—'} - - - {/* Seitenzahl */} - - onPageClick?.(row.page)} - sx={{ cursor: 'pointer' }} - > - {row.page} - - - - ); - })} - -
-
- - {/* Dialog zum Bearbeiten */} - setOpen(false)}> - Kennzahl bearbeiten - - setCurrentValue(e.target.value)} - label="Neuer Wert" - variant="outlined" - margin="dense" - /> - - - - - - - - ); - } - \ No newline at end of file + handleEditClick(row.value, index)} + /> + + + + onPageClick?.(row.page)} + sx={{ cursor: 'pointer' }} + > + {row.page} + + + + ); + })} + + + + + setOpen(false)}> + Kennzahl bearbeiten + + setCurrentValue(e.target.value)} + label="Neuer Wert" + variant="outlined" + margin="dense" + /> + + + + + + + + ); +} diff --git a/project/frontend/src/components/pdfViewer.tsx b/project/frontend/src/components/pdfViewer.tsx index 7d9c158..96e3d43 100644 --- a/project/frontend/src/components/pdfViewer.tsx +++ b/project/frontend/src/components/pdfViewer.tsx @@ -1,118 +1,124 @@ -import { useEffect, useRef, useState } from "react"; -import { Document, Page } from "react-pdf"; +import { useEffect, useRef, useState, useCallback } from "react"; +import { Document, Page, pdfjs } from "react-pdf"; import "react-pdf/dist/esm/Page/AnnotationLayer.css"; import "react-pdf/dist/esm/Page/TextLayer.css"; +import { Box, IconButton } from "@mui/material"; import ArrowCircleLeftIcon from "@mui/icons-material/ArrowCircleLeft"; import ArrowCircleRightIcon from "@mui/icons-material/ArrowCircleRight"; -import { Box, IconButton } from "@mui/material"; import { socket } from "../socket"; +pdfjs.GlobalWorkerOptions.workerSrc = new URL( + "pdfjs-dist/build/pdf.worker.min.mjs", + import.meta.url +).toString(); + interface PDFViewerProps { - pitchBookId: string; - currentPage?: number; + pitchBookId: string; + currentPage?: number; } export default function PDFViewer({ pitchBookId, currentPage }: PDFViewerProps) { - const [numPages, setNumPages] = useState(null); - const [pageNumber, setPageNumber] = useState(currentPage || 1); - const [containerWidth, setContainerWidth] = useState(null); - const [pdfKey, setPdfKey] = useState(Date.now()); - const containerRef = useRef(null); + const [numPages, setNumPages] = useState(null); + const [pageNumber, setPageNumber] = useState(currentPage || 1); + const [containerWidth, setContainerWidth] = useState(null); + const [highlightLabels, setHighlightLabels] = useState([]); + const [pdfKey, setPdfKey] = useState(Date.now()); - const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => { - setNumPages(numPages); - }; + const containerRef = useRef(null); - useEffect(() => { - const updateWidth = () => { - if (containerRef.current) { - setContainerWidth(containerRef.current.offsetWidth); - } - }; + const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => { + setNumPages(numPages); + }; - updateWidth(); - window.addEventListener("resize", updateWidth); - return () => window.removeEventListener("resize", updateWidth); - }, []); + // Resize + useEffect(() => { + const updateWidth = () => { + if (containerRef.current) { + setContainerWidth(containerRef.current.offsetWidth); + } + }; + updateWidth(); + window.addEventListener("resize", updateWidth); + return () => window.removeEventListener("resize", updateWidth); + }, []); - useEffect(() => { - if (currentPage && currentPage !== pageNumber) { - setPageNumber(currentPage); - } - }, [currentPage]); + // Highlight-Wörter setzen + useEffect(() => { + setHighlightLabels(["LTV", "Fondsmanager", "Risikoprofil"]); + }, []); - useEffect(() => { - const handleProgress = (data: { id: number; progress: number }) => { - if (data.id.toString() === pitchBookId && data.progress === 50) { - setPdfKey(Date.now()); - } - }; + // Highlight-Logik + const highlightPattern = (text: string, patterns: string[]) => { + for (const word of patterns) { + const regex = new RegExp(`(${word})`, "gi"); + text = text.replace(regex, "$1"); + } + return text; + }; - socket.on("progress", handleProgress); + const textRenderer = useCallback( + (textItem: { str: string }) => highlightPattern(textItem.str, highlightLabels), + [highlightLabels] + ); - return () => { - socket.off("progress", handleProgress); - }; - }, [pitchBookId]); + // Seitenwechsel bei Prop-Änderung + useEffect(() => { + if (currentPage && currentPage !== pageNumber) { + setPageNumber(currentPage); + } + }, [currentPage]); - return ( - - - - console.error("Es gab ein Fehler beim Laden des PDFs:", error) - } - onSourceError={(error) => console.error("Ungültige PDF:", error)} - > - {containerWidth && ( - - )} - - - - setPageNumber((p) => p - 1)} - > - - - - {pageNumber} / {numPages} - - = (numPages || 1)} - onClick={() => setPageNumber((p) => p + 1)} - > - - - - - ); -} \ No newline at end of file + // Socket: Re-Render bei Fortschritt + useEffect(() => { + const handleProgress = (data: { id: number; progress: number }) => { + if (data.id.toString() === pitchBookId && data.progress === 50) { + setPdfKey(Date.now()); // Force re-render + } + }; + socket.on("progress", handleProgress); + return () => { + socket.off("progress", handleProgress); + }; + }, [pitchBookId]); + + return ( + + + console.error("Fehler beim Laden:", error)} + onSourceError={(error) => console.error("Ungültige PDF:", error)} + > + {containerWidth && ( + + )} + + + + setPageNumber((p) => p - 1)}> + + + {pageNumber} / {numPages} + = (numPages || 1)} onClick={() => setPageNumber((p) => p + 1)}> + + + + + ); +}