frontend/31-highlight-kennzahlen #47

Closed
1924466 wants to merge 8 commits from frontend/31-highlight-kennzahlen into main
2 changed files with 234 additions and 243 deletions

View File

@ -3,44 +3,37 @@ import {
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';
} 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 = [
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 {
interface KennzahlenTableProps {
onPageClick?: (page: number) => void;
}
setSelectedLabel?: (label: string) => void;
}
// React-Komponente
export default function KennzahlenTable({ onPageClick }: KennzahlenTableProps) {
// Zustand für bearbeitbare Daten
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<number | null>(null);
// Zustände für Dialog-Funktion
const [open, setOpen] = useState(false); // Dialog anzeigen?
const [currentValue, setCurrentValue] = useState(''); // Eingabewert
const [currentIndex, setCurrentIndex] = useState<number | null>(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];
@ -54,7 +47,6 @@ import {
<>
<TableContainer component={Paper}>
<Table>
{/* Tabellenkopf */}
<TableHead>
<TableRow>
<TableCell><strong>Kennzahl</strong></TableCell>
@ -62,21 +54,20 @@ import {
<TableCell><strong>Seite</strong></TableCell>
</TableRow>
</TableHead>
{/* Tabelleninhalt */}
<TableBody>
{rows.map((row, index) => {
// Rahmenfarbe anhand Status
let borderColor = 'transparent';
if (row.status === 'error') borderColor = 'red';
else if (row.status === 'warning') borderColor = '#f6ed48';
return (
<TableRow key={index}>
{/* Kennzahl */}
<TableRow
key={index}
onClick={() => setSelectedLabel?.(row.label)}
hover
sx={{ cursor: 'pointer' }}
>
<TableCell>{row.label}</TableCell>
{/* Wert mit Status-Icons + Stift rechts */}
<TableCell>
<Box
sx={{
@ -94,8 +85,6 @@ import {
{row.status === 'warning' && <SearchIcon fontSize="small" sx={{ color: '#f6ed48' }} />}
<span>{row.value || '—'}</span>
</Box>
{/* Stift-Icon */}
<EditIcon
fontSize="small"
sx={{ color: '#555', cursor: 'pointer' }}
@ -103,8 +92,6 @@ import {
/>
</Box>
</TableCell>
{/* Seitenzahl */}
<TableCell>
<Link
component="button"
@ -121,7 +108,6 @@ import {
</Table>
</TableContainer>
{/* Dialog zum Bearbeiten */}
<Dialog open={open} onClose={() => setOpen(false)}>
<DialogTitle>Kennzahl bearbeiten</DialogTitle>
<DialogContent>
@ -141,5 +127,4 @@ import {
</Dialog>
</>
);
}
}

View File

@ -1,12 +1,17 @@
import { useEffect, useRef, useState } from "react";
import { Document, Page } from "react-pdf";
import { useEffect, useRef, useState, useCallback } from "react";

Das musste vom mergen sein. Das muss überall weg. Sonst kann ich es nicht testen...

Das musste vom mergen sein. Das muss überall weg. Sonst kann ich es nicht testen...
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;
@ -16,55 +21,68 @@ export default function PDFViewer({ pitchBookId, currentPage }: PDFViewerProps)
const [numPages, setNumPages] = useState<number | null>(null);
const [pageNumber, setPageNumber] = useState(currentPage || 1);
const [containerWidth, setContainerWidth] = useState<number | null>(null);
const [highlightLabels, setHighlightLabels] = useState<string[]>([]);
const [pdfKey, setPdfKey] = useState(Date.now());
const containerRef = useRef<HTMLDivElement>(null);
const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
setNumPages(numPages);
};
// Resize
useEffect(() => {
const updateWidth = () => {
if (containerRef.current) {
setContainerWidth(containerRef.current.offsetWidth);
}
};
updateWidth();
window.addEventListener("resize", updateWidth);
return () => window.removeEventListener("resize", updateWidth);
}, []);
// Highlight-Wörter setzen
useEffect(() => {
setHighlightLabels(["LTV", "Fondsmanager", "Risikoprofil"]);
}, []);

das auch...

das auch...
// Highlight-Logik
const highlightPattern = (text: string, patterns: string[]) => {
for (const word of patterns) {
const regex = new RegExp(`(${word})`, "gi");
text = text.replace(regex, "<mark>$1</mark>");
}
return text;
};
const textRenderer = useCallback(
(textItem: { str: string }) => highlightPattern(textItem.str, highlightLabels),
[highlightLabels]
);
// Seitenwechsel bei Prop-Änderung
useEffect(() => {
if (currentPage && currentPage !== pageNumber) {
setPageNumber(currentPage);
}
}, [currentPage]);
// Socket: Re-Render bei Fortschritt
useEffect(() => {
const handleProgress = (data: { id: number; progress: number }) => {
if (data.id.toString() === pitchBookId && data.progress === 50) {
setPdfKey(Date.now());
setPdfKey(Date.now()); // Force re-render
}
};
socket.on("progress", handleProgress);
return () => {
socket.off("progress", handleProgress);
};
}, [pitchBookId]);
return (
<Box
display="flex"
flexDirection="column"
justifyContent="center"
alignItems="center"
width="100%"
height="100%"
p={2}
>
<Box display="flex" flexDirection="column" justifyContent="center" alignItems="center" width="100%" height="100%" p={2}>
<Box
ref={containerRef}
sx={{
@ -80,36 +98,24 @@ export default function PDFViewer({ pitchBookId, currentPage }: PDFViewerProps)
key={pdfKey}
file={`http://localhost:5050/api/pitch_book/${pitchBookId}/download`}
onLoadSuccess={onDocumentLoadSuccess}
onLoadError={(error) =>
console.error("Es gab ein Fehler beim Laden des PDFs:", error)
}
onLoadError={(error) => console.error("Fehler beim Laden:", error)}
onSourceError={(error) => console.error("Ungültige PDF:", error)}
>
{containerWidth && (
<Page pageNumber={pageNumber} width={containerWidth * 0.8} />
<Page
pageNumber={pageNumber}
width={containerWidth * 0.8}
customTextRenderer={textRenderer}
/>
)}
</Document>
</Box>
<Box
mt={2}
display="flex"
alignItems="center"
justifyContent="center"
gap={1}
>
<IconButton
disabled={pageNumber <= 1}
onClick={() => setPageNumber((p) => p - 1)}
>
<Box mt={2} display="flex" alignItems="center" justifyContent="center" gap={1}>
<IconButton disabled={pageNumber <= 1} onClick={() => setPageNumber((p) => p - 1)}>
<ArrowCircleLeftIcon fontSize="large" />
</IconButton>
<span>
{pageNumber} / {numPages}
</span>
<IconButton
disabled={pageNumber >= (numPages || 1)}
onClick={() => setPageNumber((p) => p + 1)}
>
<span>{pageNumber} / {numPages}</span>
<IconButton disabled={pageNumber >= (numPages || 1)} onClick={() => setPageNumber((p) => p + 1)}>
<ArrowCircleRightIcon fontSize="large" />
</IconButton>
</Box>