diff --git a/project/frontend/src/components/KennzahlenTable.tsx b/project/frontend/src/components/KennzahlenTable.tsx index 3f2fe75..acc8d47 100644 --- a/project/frontend/src/components/KennzahlenTable.tsx +++ b/project/frontend/src/components/KennzahlenTable.tsx @@ -3,7 +3,6 @@ import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; import SearchIcon from "@mui/icons-material/Search"; import { Box, - IconButton, Link, Paper, Table, @@ -13,6 +12,7 @@ import { TableHead, TableRow, TextField, + Tooltip, } from "@mui/material"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useNavigate } from "@tanstack/react-router"; @@ -32,7 +32,7 @@ const SETTINGS = [ interface KennzahlenTableProps { onPageClick?: (page: number) => void; - pdfId: string; // Neue Prop für die PDF-ID + pdfId: string; data: { [key: string]: { label: string; @@ -44,7 +44,6 @@ interface KennzahlenTableProps { }; } -// React-Komponente export default function KennzahlenTable({ onPageClick, data, @@ -121,6 +120,16 @@ export default function KennzahlenTable({ } }; + const handleNavigateToDetail = (settingName: string) => { + navigate({ + to: "/extractedResult/$pitchBook/$kpi", + params: { + pitchBook: pdfId, + kpi: settingName, + }, + }); + }; + return ( @@ -132,7 +141,7 @@ export default function KennzahlenTable({ Wert - + Seite @@ -147,89 +156,122 @@ export default function KennzahlenTable({ })) .map((row) => { let borderColor = "transparent"; - if ( - row.setting.mandatory && + const hasMultipleValues = row.extractedValues.length > 1; + const hasNoValue = row.setting.mandatory && (row.extractedValues.length === 0 || - row.extractedValues.at(0)?.entity === "") - ) + row.extractedValues.at(0)?.entity === ""); + + if (hasNoValue) { borderColor = "red"; - else if (row.extractedValues.length > 1) borderColor = "#f6ed48"; + } else if (hasMultipleValues) { + borderColor = "#f6ed48"; + } return ( {row.setting.name} - startEditing( - row.extractedValues.at(0)?.entity || "", - row.setting.name, - ) - } + onClick={() => { + // Only allow inline editing for non-multiple value cells + if (!hasMultipleValues) { + startEditing( + row.extractedValues.at(0)?.entity || "", + row.setting.name, + ); + } else { + // Navigate to detail page for multiple values + handleNavigateToDetail(row.setting.name); + } + }} > - + {hasMultipleValues ? ( + + Problem + Mehrere Werte für die Kennzahl gefunden. + > + } + placement="bottom" + arrow + > + + + + {row.extractedValues.at(0)?.entity || "—"} + + + + + + ) : ( - {row.setting.mandatory && - row.extractedValues.length === 0 && ( + + {hasNoValue && ( )} - {editingIndex === row.setting.name ? ( - setEditValue(e.target.value)} - onKeyDown={(e) => - handleKeyPress(e, row.setting.name) - } - onBlur={() => handleSave(row.setting.name)} - autoFocus - size="small" - fullWidth - variant="standard" - sx={{ margin: "-8px 0" }} - /> - ) : ( - - {row.extractedValues.at(0)?.entity || "—"} - - )} - - {row.extractedValues.length > 1 && ( - - navigate({ - to: "/extractedResult/$pitchBook/$kpi", - params: { - pitchBook: pdfId, - kpi: row.setting.name, - }, - }) - } - > - - - )} - {row.extractedValues.length <= 1 && ( + {editingIndex === row.setting.name ? ( + setEditValue(e.target.value)} + onKeyDown={(e) => + handleKeyPress(e, row.setting.name) + } + onBlur={() => handleSave(row.setting.name)} + autoFocus + size="small" + fullWidth + variant="standard" + sx={{ margin: "-8px 0" }} + /> + ) : ( + + {row.extractedValues.at(0)?.entity || "—"} + + )} + - )} - + + )} - + @@ -262,4 +304,4 @@ export default function KennzahlenTable({ ); -} +} \ No newline at end of file diff --git a/project/frontend/src/components/pdfViewer.tsx b/project/frontend/src/components/pdfViewer.tsx index a24fa9f..5abc1f4 100644 --- a/project/frontend/src/components/pdfViewer.tsx +++ b/project/frontend/src/components/pdfViewer.tsx @@ -10,11 +10,13 @@ import { socket } from "../socket"; interface PDFViewerProps { pitchBookId: string; currentPage?: number; + onPageChange?: (page: number) => void; } export default function PDFViewer({ pitchBookId, currentPage, + onPageChange, }: PDFViewerProps) { const [numPages, setNumPages] = useState(null); const [pageNumber, setPageNumber] = useState(currentPage || 1); @@ -42,7 +44,7 @@ export default function PDFViewer({ if (currentPage && currentPage !== pageNumber) { setPageNumber(currentPage); } - }, [currentPage, pageNumber]); + }, [currentPage]); useEffect(() => { const handleProgress = (data: { id: number; progress: number }) => { @@ -58,6 +60,11 @@ export default function PDFViewer({ }; }, [pitchBookId]); + const handlePageChange = (newPage: number) => { + setPageNumber(newPage); + onPageChange?.(newPage); + }; + return ( console.error("Ungültige PDF:", error)} > {containerWidth && ( - + )} setPageNumber((p) => p - 1)} + onClick={() => handlePageChange(pageNumber - 1)} > - {pageNumber} / {numPages} - + {pageNumber} / {numPages} + = (numPages || 1)} - onClick={() => setPageNumber((p) => p + 1)} + onClick={() => handlePageChange(pageNumber + 1)} > ); -} +} \ No newline at end of file diff --git a/project/frontend/src/routes/extractedResult.$pitchBook.tsx b/project/frontend/src/routes/extractedResult.$pitchBook.tsx index db7c47e..c2d57f7 100644 --- a/project/frontend/src/routes/extractedResult.$pitchBook.tsx +++ b/project/frontend/src/routes/extractedResult.$pitchBook.tsx @@ -41,8 +41,7 @@ function ExtractedResultsPage() { }} /> - Kennzahlen extrahiert aus: - FONDSNAME: TODO + Extrahierte Kennzahlen @@ -59,7 +58,8 @@ function ExtractedResultsPage() { elevation={2} sx={{ width: "45%", - height: "100%", + maxHeight: "100%", + height: "fit-content", borderRadius: 2, backgroundColor: "#eeeeee", padding: 2, @@ -76,23 +76,33 @@ function ExtractedResultsPage() { display="flex" flexDirection="column" justifyContent="space-between" - gap={5} - sx={{ width: "55%", height: "95%" }} + gap={3} + sx={{ + width: "55%", + maxHeight: "95%" + }} > - + - + Kennzahlenzeile kopieren @@ -109,4 +119,4 @@ function ExtractedResultsPage() { ); -} +} \ No newline at end of file diff --git a/project/frontend/src/routes/extractedResult_.$pitchBook.$kpi.tsx b/project/frontend/src/routes/extractedResult_.$pitchBook.$kpi.tsx index b3e9880..ccf34b8 100644 --- a/project/frontend/src/routes/extractedResult_.$pitchBook.$kpi.tsx +++ b/project/frontend/src/routes/extractedResult_.$pitchBook.$kpi.tsx @@ -1,27 +1,369 @@ -import { useSuspenseQuery } from "@tanstack/react-query"; -import { createFileRoute } from "@tanstack/react-router"; +import { + Box, + Button, Dialog, DialogActions, DialogContent, + DialogContentText, + DialogTitle, + IconButton, + Link, + Paper, + Radio, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, + Typography +} from "@mui/material"; +import {useMutation, useQueryClient, useSuspenseQuery} from "@tanstack/react-query"; +import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import {useEffect, useState} from "react"; +import PDFViewer from "../components/pdfViewer"; import { kpiQueryOptions } from "../util/query"; +import ArrowBackIcon from "@mui/icons-material/ArrowBack"; +import {fetchPutKPI} from "../util/api"; export const Route = createFileRoute("/extractedResult_/$pitchBook/$kpi")({ - component: RouteComponent, + component: ExtractedResultsPage, loader: ({ context: { queryClient }, params: { pitchBook } }) => queryClient.ensureQueryData(kpiQueryOptions(pitchBook)), }); -function RouteComponent() { - const { pitchBook, kpi } = Route.useParams(); +function ExtractedResultsPage() { + const params = Route.useParams() as { pitchBook: string; kpi: string }; + const { pitchBook, kpi } = params; + const navigate = useNavigate(); + const queryClient = useQueryClient(); const { - data: { [kpi.toUpperCase()]: kpiValues }, + data: kpiData } = useSuspenseQuery(kpiQueryOptions(pitchBook)); + const kpiValues = kpiData[kpi.toUpperCase()] || []; + const [selectedIndex, setSelectedIndex] = useState(0); + const [currentPage, setCurrentPage] = useState(kpiValues[0]?.page || 1); + const [showConfirmDialog, setShowConfirmDialog] = useState(false); + const [hasChanges, setHasChanges] = useState(false); + const [customValue, setCustomValue] = useState(''); + const originalValue = kpiValues[0]?.entity || ''; + const selectedValue = selectedIndex === -1 ? customValue : (kpiValues[selectedIndex]?.entity || ''); + + useEffect(() => { + setHasChanges(selectedValue !== originalValue); + }, [selectedValue, originalValue]); + + const { mutate: updateKPI } = useMutation({ + mutationFn: () => { + const updatedData = { ...kpiData }; + updatedData[kpi.toUpperCase()] = [{ + ...kpiValues[0], + entity: selectedValue + }]; + return fetchPutKPI(Number(pitchBook), updatedData); + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["pitchBookKPI", pitchBook], + }); + navigate({ + to: "/extractedResult/$pitchBook", + params: { pitchBook } + }); + }, + onError: (error) => { + console.error('Error updating KPI:', error); + } + }); + + const handleRadioChange = (event: React.ChangeEvent) => { + const value = event.target.value; + if (value === 'custom') { + setSelectedIndex(-1); + } else { + const index = parseInt(value); + setSelectedIndex(index); + setCustomValue(''); + } + }; + + const handleCustomValueChange = (event: React.ChangeEvent) => { + const value = event.target.value; + setCustomValue(value); + setSelectedIndex(-1); + }; + + const handleRowClick = (index: number) => { + setSelectedIndex(index); + setCustomValue(''); + }; + + const handleBackClick = () => { + if (hasChanges) { + setShowConfirmDialog(true); + } else { + navigate({ + to: "/extractedResult/$pitchBook", + params: { pitchBook } + }); + } + }; + + const handleConfirmDiscard = () => { + setShowConfirmDialog(false); + navigate({ + to: "/extractedResult/$pitchBook", + params: { pitchBook } + }); + }; + + const handleCancelDiscard = () => { + setShowConfirmDialog(false); + }; + + const handleAcceptReview = () => { + updateKPI(); + }; + return ( - - {kpiValues.map((e) => ( - - {e.label}: {e.entity} - - ))} - + + + + + + + Überprüfung der Kennzahl: {kpi} + + + + + + + + + + + Gefundene Werte + + + Seite + + + + + {kpiValues.map((item, index) => ( + handleRowClick(index)} + > + + + + {item.entity} + + + + { + e.stopPropagation(); + setCurrentPage(item.page); + }} + sx={{ cursor: 'pointer' }} + > + {item.page} + + + + ))} + + + + { + setSelectedIndex(-1); + }} + > + + { + e.stopPropagation(); + }} + /> + + + + + + + + + + + + + + Überprüfung Annehmen + + + + + + + + Achtung + + + + Alle vorgenommenen Änderungen werden verworfen. + + + + + Abbrechen + + + Bestätigen + + + + ); -} +} \ No newline at end of file