From 082317318aac36893cdb5fa3978b0a92eabad25a Mon Sep 17 00:00:00 2001 From: Jaronim Pracht Date: Fri, 13 Jun 2025 21:29:34 +0200 Subject: [PATCH] Update ExtractedResultsPage to fetch and pass settings to KennzahlenTable --- .../frontend/src/components/ConfigTable.tsx | 615 +++++++++--------- .../src/components/KennzahlenTable.tsx | 38 +- .../src/routes/extractedResult.$pitchBook.tsx | 23 +- project/frontend/src/util/api.ts | 14 +- project/frontend/src/util/query.ts | 8 +- 5 files changed, 377 insertions(+), 321 deletions(-) diff --git a/project/frontend/src/components/ConfigTable.tsx b/project/frontend/src/components/ConfigTable.tsx index 017d113..74675d7 100644 --- a/project/frontend/src/components/ConfigTable.tsx +++ b/project/frontend/src/components/ConfigTable.tsx @@ -1,322 +1,353 @@ -import { Box, Tooltip, CircularProgress, Typography } from "@mui/material"; import DragIndicatorIcon from "@mui/icons-material/DragIndicator"; -import { useEffect, useState } from "react"; +import { Box, CircularProgress, Tooltip, Typography } from "@mui/material"; import { useNavigate } from "@tanstack/react-router"; +import { useEffect, useState } from "react"; import type { Kennzahl } from "../types/kpi"; import { getDisplayType } from "../types/kpi"; +import { fetchKennzahlen as fetchK } from "../util/api"; export function ConfigTable() { - const navigate = useNavigate(); - const [kennzahlen, setKennzahlen] = useState([]); - const [draggedItem, setDraggedItem] = useState(null); - const [isUpdatingPositions, setIsUpdatingPositions] = useState(false); - const [loading, setLoading] = useState(true); + const navigate = useNavigate(); + const [kennzahlen, setKennzahlen] = useState([]); + const [draggedItem, setDraggedItem] = useState(null); + const [isUpdatingPositions, setIsUpdatingPositions] = useState(false); + const [loading, setLoading] = useState(true); - useEffect(() => { - const fetchKennzahlen = async () => { - while (true) { - try { - console.log('Fetching kennzahlen from API...'); - const response = await fetch(`http://localhost:5050/api/kpi_setting/`); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } + useEffect(() => { + const fetchKennzahlen = async () => { + while (true) { + try { + console.log("Fetching kennzahlen from API..."); + const data = await fetchK(); + console.log("Fetched kennzahlen:", data); + const sortedData = data.sort( + (a: Kennzahl, b: Kennzahl) => a.position - b.position, + ); + setKennzahlen(sortedData); + setLoading(false); + break; + } catch (err) { + console.error("Error fetching kennzahlen:", err); + await new Promise((resolve) => setTimeout(resolve, 2000)); + } + } + }; - const data = await response.json(); - console.log('Fetched kennzahlen:', data); - const sortedData = data.sort((a: Kennzahl, b: Kennzahl) => a.position - b.position); - setKennzahlen(sortedData); - setLoading(false); - break; - } catch (err) { - console.error('Error fetching kennzahlen:', err); - await new Promise(resolve => setTimeout(resolve, 2000)); - } - } - }; + fetchKennzahlen(); + }, []); - fetchKennzahlen(); - }, []); + const handleToggleActive = async (id: number) => { + const kennzahl = kennzahlen.find((k) => k.id === id); + if (!kennzahl) return; - const handleToggleActive = async (id: number) => { - const kennzahl = kennzahlen.find(k => k.id === id); - if (!kennzahl) return; + try { + const response = await fetch( + `http://localhost:5050/api/kpi_setting/${id}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + active: !kennzahl.active, + }), + }, + ); - try { - const response = await fetch(`http://localhost:5050/api/kpi_setting/${id}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - active: !kennzahl.active - }), - }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } + const updatedKennzahl = await response.json(); + setKennzahlen((prev) => + prev.map((item) => (item.id === id ? updatedKennzahl : item)), + ); + } catch (err) { + console.error("Error toggling active status:", err); + setKennzahlen((prev) => + prev.map((item) => (item.id === id ? kennzahl : item)), + ); + } + }; - const updatedKennzahl = await response.json(); - setKennzahlen(prev => - prev.map(item => - item.id === id ? updatedKennzahl : item - ) - ); - } catch (err) { - console.error('Error toggling active status:', err); - setKennzahlen(prev => - prev.map(item => - item.id === id ? kennzahl : item - ) - ); - } - }; + const updatePositionsInBackend = async (reorderedKennzahlen: Kennzahl[]) => { + setIsUpdatingPositions(true); + try { + const positionUpdates = reorderedKennzahlen.map((kennzahl, index) => ({ + id: kennzahl.id, + position: index + 1, + })); - const updatePositionsInBackend = async (reorderedKennzahlen: Kennzahl[]) => { - setIsUpdatingPositions(true); - try { - const positionUpdates = reorderedKennzahlen.map((kennzahl, index) => ({ - id: kennzahl.id, - position: index + 1 - })); + const response = await fetch( + `http://localhost:5050/api/kpi_setting/update-kpi-positions`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(positionUpdates), + }, + ); - const response = await fetch(`http://localhost:5050/api/kpi_setting/update-kpi-positions`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(positionUpdates), - }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } + const updatedKennzahlen = await response.json(); + setKennzahlen(updatedKennzahlen); + } catch (err) { + console.error("Error updating positions:", err); + window.location.reload(); + } finally { + setIsUpdatingPositions(false); + } + }; - const updatedKennzahlen = await response.json(); - setKennzahlen(updatedKennzahlen); - } catch (err) { - console.error('Error updating positions:', err); - window.location.reload(); - } finally { - setIsUpdatingPositions(false); - } - }; + const handleDragStart = ( + e: React.DragEvent, + item: Kennzahl, + ) => { + setDraggedItem(item); + e.dataTransfer.effectAllowed = "move"; + }; - const handleDragStart = (e: React.DragEvent, item: Kennzahl) => { - setDraggedItem(item); - e.dataTransfer.effectAllowed = "move"; - }; + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + e.dataTransfer.dropEffect = "move"; + }; - const handleDragOver = (e: React.DragEvent) => { - e.preventDefault(); - e.dataTransfer.dropEffect = "move"; - }; + const handleDrop = async ( + e: React.DragEvent, + targetItem: Kennzahl, + ) => { + e.preventDefault(); + if (!draggedItem || draggedItem.id === targetItem.id) return; - const handleDrop = async (e: React.DragEvent, targetItem: Kennzahl) => { - e.preventDefault(); - if (!draggedItem || draggedItem.id === targetItem.id) return; + const draggedIndex = kennzahlen.findIndex( + (item) => item.id === draggedItem.id, + ); + const targetIndex = kennzahlen.findIndex( + (item) => item.id === targetItem.id, + ); - const draggedIndex = kennzahlen.findIndex(item => item.id === draggedItem.id); - const targetIndex = kennzahlen.findIndex(item => item.id === targetItem.id); + const newKennzahlen = [...kennzahlen]; + const [removed] = newKennzahlen.splice(draggedIndex, 1); + newKennzahlen.splice(targetIndex, 0, removed); - const newKennzahlen = [...kennzahlen]; - const [removed] = newKennzahlen.splice(draggedIndex, 1); - newKennzahlen.splice(targetIndex, 0, removed); + setKennzahlen(newKennzahlen); + setDraggedItem(null); + await updatePositionsInBackend(newKennzahlen); + }; - setKennzahlen(newKennzahlen); - setDraggedItem(null); - await updatePositionsInBackend(newKennzahlen); - }; + const handleDragEnd = () => { + setDraggedItem(null); + }; - const handleDragEnd = () => { - setDraggedItem(null); - }; + const handleRowClick = (kennzahl: Kennzahl, e: React.MouseEvent) => { + if (draggedItem || isUpdatingPositions) { + return; + } - const handleRowClick = (kennzahl: Kennzahl, e: React.MouseEvent) => { - if (draggedItem || isUpdatingPositions) { - return; - } + const target = e.target as HTMLElement; + if ( + target.tagName === "INPUT" && + (target as HTMLInputElement).type === "checkbox" + ) { + return; + } - const target = e.target as HTMLElement; - if (target.tagName === 'INPUT' && (target as HTMLInputElement).type === 'checkbox') { - return; - } + if (target.closest(".drag-handle")) { + return; + } - if (target.closest('.drag-handle')) { - return; - } + console.log("Navigating to detail page for KPI:", kennzahl); + console.log("KPI ID:", kennzahl.id); - console.log('Navigating to detail page for KPI:', kennzahl); - console.log('KPI ID:', kennzahl.id); + navigate({ + to: `/config-detail/$kpiId`, + params: { kpiId: kennzahl.id.toString() }, + }); + }; - navigate({ - to: `/config-detail/$kpiId`, - params: { kpiId: kennzahl.id.toString() } - }); - }; + if (loading) { + return ( + + + Lade Kennzahlen-Konfiguration... + + ); + } - if (loading) { - return ( - - - Lade Kennzahlen-Konfiguration... - - ); - } - - return ( - - - - - - - - - - - - {kennzahlen.map((kennzahl) => ( - handleDragStart(e, kennzahl)} - onDragOver={handleDragOver} - onDrop={(e) => handleDrop(e, kennzahl)} - onDragEnd={handleDragEnd} - onClick={(e) => handleRowClick(kennzahl, e)} - style={{ - borderBottom: "1px solid #e0e0e0", - cursor: isUpdatingPositions ? "default" : "pointer", - backgroundColor: draggedItem?.id === kennzahl.id ? "#f0f0f0" : "white", - opacity: draggedItem?.id === kennzahl.id ? 0.5 : 1 - }} - onMouseEnter={(e) => { - if (!draggedItem && !isUpdatingPositions) { - e.currentTarget.style.backgroundColor = "#f9f9f9"; - } - }} - onMouseLeave={(e) => { - if (!draggedItem && !isUpdatingPositions) { - e.currentTarget.style.backgroundColor = "white"; - } - }} - > - - - - - - ))} - -
- - Aktiv - - Name - - Format -
-
- - Neuanordnung der Kennzahlen
- Hier können Sie die Kennzahlen nach Belieben per Drag and Drop neu anordnen. - - } - placement="left" - arrow - > - -
-
-
- handleToggleActive(kennzahl.id)} - disabled={isUpdatingPositions} - style={{ - width: "18px", - height: "18px", - cursor: isUpdatingPositions ? "default" : "pointer", - accentColor: "#383838" - }} - onClick={(e) => e.stopPropagation()} - /> - - - {kennzahl.name} - - - - {getDisplayType(kennzahl.type)} - -
-
- ); -} \ No newline at end of file + return ( + + + + + + + + + + + {kennzahlen.map((kennzahl) => ( + handleDragStart(e, kennzahl)} + onDragOver={handleDragOver} + onDrop={(e) => handleDrop(e, kennzahl)} + onDragEnd={handleDragEnd} + onClick={(e) => handleRowClick(kennzahl, e)} + style={{ + borderBottom: "1px solid #e0e0e0", + cursor: isUpdatingPositions ? "default" : "pointer", + backgroundColor: + draggedItem?.id === kennzahl.id ? "#f0f0f0" : "white", + opacity: draggedItem?.id === kennzahl.id ? 0.5 : 1, + }} + onMouseEnter={(e) => { + if (!draggedItem && !isUpdatingPositions) { + e.currentTarget.style.backgroundColor = "#f9f9f9"; + } + }} + onMouseLeave={(e) => { + if (!draggedItem && !isUpdatingPositions) { + e.currentTarget.style.backgroundColor = "white"; + } + }} + > + + + + + + ))} + +
+ + Aktiv + + Name + + Format +
+
+ + Neuanordnung der Kennzahlen +
+ Hier können Sie die Kennzahlen nach Belieben per Drag + and Drop neu anordnen. + + } + placement="left" + arrow + > + +
+
+
+ handleToggleActive(kennzahl.id)} + disabled={isUpdatingPositions} + style={{ + width: "18px", + height: "18px", + cursor: isUpdatingPositions ? "default" : "pointer", + accentColor: "#383838", + }} + onClick={(e) => e.stopPropagation()} + /> + + + {kennzahl.name} + + + + {getDisplayType(kennzahl.type)} + +
+
+ ); +} diff --git a/project/frontend/src/components/KennzahlenTable.tsx b/project/frontend/src/components/KennzahlenTable.tsx index acc8d47..406798e 100644 --- a/project/frontend/src/components/KennzahlenTable.tsx +++ b/project/frontend/src/components/KennzahlenTable.tsx @@ -1,3 +1,4 @@ +import type { Kennzahl } from "@/types/kpi"; import EditIcon from "@mui/icons-material/Edit"; import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline"; import SearchIcon from "@mui/icons-material/Search"; @@ -20,19 +21,10 @@ import { useState } from "react"; import type { KeyboardEvent } from "react"; import { fetchPutKPI } from "../util/api"; -const SETTINGS = [ - { name: "Rendite", position: 1, active: true, mandatory: true }, - { name: "Ausschüttungsrendite", position: 2, active: true, mandatory: true }, - { name: "Laufzeit", position: 3, active: true, mandatory: true }, - { name: "Länderallokation", position: 4, active: true, mandatory: true }, - { name: "Managmentgebühren", position: 5, active: true, mandatory: true }, - { name: "Risikoprofil", position: 6, active: false, mandatory: true }, - { name: "Irgendwas", position: 7, active: true, mandatory: true }, -]; - interface KennzahlenTableProps { onPageClick?: (page: number) => void; pdfId: string; + settings: Kennzahl[]; data: { [key: string]: { label: string; @@ -48,6 +40,7 @@ export default function KennzahlenTable({ onPageClick, data, pdfId, + settings, }: KennzahlenTableProps) { const [editingIndex, setEditingIndex] = useState(""); const [editValue, setEditValue] = useState(""); @@ -148,7 +141,8 @@ export default function KennzahlenTable({ - {SETTINGS.filter((setting) => setting.active) + {settings + .filter((setting) => setting.active) .sort((a, b) => a.position - b.position) .map((setting) => ({ setting: setting, @@ -157,7 +151,8 @@ export default function KennzahlenTable({ .map((row) => { let borderColor = "transparent"; const hasMultipleValues = row.extractedValues.length > 1; - const hasNoValue = row.setting.mandatory && + const hasNoValue = + row.setting.mandatory && (row.extractedValues.length === 0 || row.extractedValues.at(0)?.entity === ""); @@ -188,7 +183,8 @@ export default function KennzahlenTable({ - Problem
+ Problem +
Mehrere Werte für die Kennzahl gefunden. } @@ -205,9 +201,9 @@ export default function KennzahlenTable({ justifyContent: "space-between", width: "100%", cursor: "pointer", - '&:hover': { - backgroundColor: '#f5f5f5' - } + "&:hover": { + backgroundColor: "#f5f5f5", + }, }} > - +
) : ( @@ -304,4 +300,4 @@ export default function KennzahlenTable({ ); -} \ 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 c2d57f7..6878542 100644 --- a/project/frontend/src/routes/extractedResult.$pitchBook.tsx +++ b/project/frontend/src/routes/extractedResult.$pitchBook.tsx @@ -5,12 +5,15 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { useState } from "react"; import KennzahlenTable from "../components/KennzahlenTable"; import PDFViewer from "../components/pdfViewer"; -import { kpiQueryOptions } from "../util/query"; +import { kpiQueryOptions, settingsQueryOptions } from "../util/query"; export const Route = createFileRoute("/extractedResult/$pitchBook")({ component: ExtractedResultsPage, loader: ({ context: { queryClient }, params: { pitchBook } }) => - queryClient.ensureQueryData(kpiQueryOptions(pitchBook)), + Promise.allSettled([ + queryClient.ensureQueryData(kpiQueryOptions(pitchBook)), + queryClient.ensureQueryData(settingsQueryOptions()), + ]), }); function ExtractedResultsPage() { @@ -26,6 +29,7 @@ function ExtractedResultsPage() { }[status]; const { data: kpi } = useSuspenseQuery(kpiQueryOptions(pitchBook)); + const { data: settings } = useSuspenseQuery(settingsQueryOptions()); return ( @@ -67,6 +71,7 @@ function ExtractedResultsPage() { }} > - +