Merge pull request 'Data-Fetching Teil 1' (#59) from #53-data-fetching into main
Reviewed-on: #59pull/61/head
commit
211bb9a9d1
|
|
@ -1,6 +1,3 @@
|
|||
from flask_socketio import SocketIO
|
||||
|
||||
socketio = SocketIO(
|
||||
cors_allowed_origins=["http://localhost:8080", "http://localhost:3000"],
|
||||
transports=["polling", "websocket"],
|
||||
)
|
||||
socketio = SocketIO(cors_allowed_origins="*")
|
||||
|
|
|
|||
|
|
@ -1,211 +1,265 @@
|
|||
import EditIcon from "@mui/icons-material/Edit";
|
||||
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import {
|
||||
Table, TableBody, TableCell, TableContainer,
|
||||
TableHead, TableRow, Paper, Box,
|
||||
TextField, 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, useEffect } from 'react';
|
||||
import type { KeyboardEvent } from 'react';
|
||||
|
||||
const API_BASE_URL = 'http://localhost:5050'; // Korrigierter Port für den Coordinator-Service
|
||||
|
||||
interface Kennzahl {
|
||||
pdf_id: string;
|
||||
label: string;
|
||||
value: string;
|
||||
page: number;
|
||||
status: 'ok' | 'error' | 'warning';
|
||||
}
|
||||
|
||||
interface KennzahlenTableProps {
|
||||
onPageClick?: (page: number) => void;
|
||||
pdfId?: string; // Neue Prop für die PDF-ID
|
||||
}
|
||||
|
||||
// React-Komponente
|
||||
export default function KennzahlenTable({ onPageClick, pdfId = 'example' }: KennzahlenTableProps) {
|
||||
const [rows, setRows] = useState<Kennzahl[]>([]);
|
||||
const [editingIndex, setEditingIndex] = useState<number | null>(null);
|
||||
const [editValue, setEditValue] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Initialisiere Beispieldaten
|
||||
const initializeData = async () => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/kennzahlen/init`, {
|
||||
method: 'POST'
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Fehler beim Initialisieren der Daten');
|
||||
}
|
||||
// Lade die Daten nach der Initialisierung
|
||||
await fetchKennzahlen();
|
||||
} catch (err) {
|
||||
setError('Fehler beim Initialisieren der Daten');
|
||||
console.error('Fehler:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// Lade Kennzahlen vom Backend
|
||||
const fetchKennzahlen = async () => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/kennzahlen?pdf_id=${pdfId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Fehler beim Laden der Kennzahlen');
|
||||
}
|
||||
const data = await response.json();
|
||||
if (data.length === 0) {
|
||||
// Wenn keine Daten vorhanden sind, initialisiere Beispieldaten
|
||||
await initializeData();
|
||||
} else {
|
||||
setRows(data);
|
||||
setError(null);
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Fehler beim Laden der Daten');
|
||||
console.error('Fehler:', err);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Lade Daten beim ersten Render oder wenn sich die PDF-ID ändert
|
||||
useEffect(() => {
|
||||
fetchKennzahlen();
|
||||
}, [pdfId]);
|
||||
|
||||
// Funktion zum Senden der PUT-Anfrage
|
||||
const updateKennzahl = async (label: string, value: string) => {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/kennzahlen/${label}?pdf_id=${pdfId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ value })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Fehler beim Speichern der Kennzahl');
|
||||
}
|
||||
|
||||
// Lade die Daten neu nach dem Update
|
||||
await fetchKennzahlen();
|
||||
} catch (error) {
|
||||
console.error('Fehler:', error);
|
||||
setError('Fehler beim Speichern der Änderungen');
|
||||
}
|
||||
};
|
||||
|
||||
// Bearbeitung starten
|
||||
const startEditing = (value: string, index: number) => {
|
||||
setEditingIndex(index);
|
||||
setEditValue(value);
|
||||
};
|
||||
|
||||
// Bearbeitung beenden und Wert speichern
|
||||
const handleSave = async (index: number) => {
|
||||
await updateKennzahl(rows[index].label, editValue);
|
||||
setEditingIndex(null);
|
||||
};
|
||||
|
||||
// Tastatureingaben verarbeiten
|
||||
const handleKeyPress = (e: KeyboardEvent<HTMLDivElement>, index: number) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSave(index);
|
||||
} else if (e.key === 'Escape') {
|
||||
setEditingIndex(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <div>Lade Daten...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <Box sx={{ color: 'error.main' }}>{error}</Box>;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell><strong>Kennzahl</strong></TableCell>
|
||||
<TableCell><strong>Wert</strong></TableCell>
|
||||
<TableCell><strong>Seite</strong></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{rows.map((row, index) => {
|
||||
let borderColor = 'transparent';
|
||||
if (row.status === 'error') borderColor = 'red';
|
||||
else if (row.status === 'warning') borderColor = '#f6ed48';
|
||||
|
||||
return (
|
||||
<TableRow key={index}>
|
||||
<TableCell>{row.label}</TableCell>
|
||||
<TableCell onClick={() => startEditing(row.value, index)}>
|
||||
<Box
|
||||
sx={{
|
||||
border: `2px solid ${borderColor}`,
|
||||
borderRadius: 1,
|
||||
padding: '4px 8px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%',
|
||||
cursor: 'text',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, width: '100%' }}>
|
||||
{row.status === 'error' && <ErrorOutlineIcon fontSize="small" color="error" />}
|
||||
{row.status === 'warning' && <SearchIcon fontSize="small" sx={{ color: '#f6ed48' }} />}
|
||||
{editingIndex === index ? (
|
||||
<TextField
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onKeyDown={(e) => handleKeyPress(e, index)}
|
||||
onBlur={() => handleSave(index)}
|
||||
autoFocus
|
||||
size="small"
|
||||
fullWidth
|
||||
variant="standard"
|
||||
sx={{ margin: '-8px 0' }}
|
||||
/>
|
||||
) : (
|
||||
<span>{row.value || '—'}</span>
|
||||
)}
|
||||
</Box>
|
||||
<EditIcon
|
||||
fontSize="small"
|
||||
sx={{ color: '#555', cursor: 'pointer' }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
startEditing(row.value, index);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Link
|
||||
component="button"
|
||||
onClick={() => onPageClick?.(row.page)}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
>
|
||||
{row.page}
|
||||
</Link>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
|
||||
Box,
|
||||
IconButton,
|
||||
Link,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
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; // Neue Prop für die PDF-ID
|
||||
data: {
|
||||
[key: string]: {
|
||||
label: string;
|
||||
entity: string;
|
||||
page: number;
|
||||
status: string;
|
||||
source: string;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
// React-Komponente
|
||||
export default function KennzahlenTable({
|
||||
onPageClick,
|
||||
data,
|
||||
pdfId,
|
||||
}: KennzahlenTableProps) {
|
||||
const [editingIndex, setEditingIndex] = useState<string>("");
|
||||
const [editValue, setEditValue] = useState("");
|
||||
const navigate = useNavigate({ from: "/extractedResult/$pitchBook" });
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate } = useMutation({
|
||||
mutationFn: (id: string) => {
|
||||
const key = id.toUpperCase();
|
||||
const updatedData = { ...data };
|
||||
updatedData[key] = data[key]?.map((item) => ({
|
||||
...item,
|
||||
entity: editValue,
|
||||
})) || [{ label: key, entity: editValue }];
|
||||
return fetchPutKPI(Number(pdfId), updatedData);
|
||||
},
|
||||
onMutate: async (id: string) => {
|
||||
await queryClient.cancelQueries({
|
||||
queryKey: ["pitchBookKPI", pdfId],
|
||||
});
|
||||
|
||||
const snapshot = queryClient.getQueryData(["pitchBookKPI", pdfId]);
|
||||
|
||||
const key = id.toUpperCase();
|
||||
|
||||
queryClient.setQueryData(["pitchBookKPI", pdfId], () => {
|
||||
const updatedData = { ...data };
|
||||
updatedData[key] = data[key]?.map((item) => ({
|
||||
...item,
|
||||
entity: editValue,
|
||||
})) || [{ label: key, entity: editValue }];
|
||||
return updatedData;
|
||||
});
|
||||
|
||||
return () => {
|
||||
queryClient.setQueryData(["pitchBookKPI", pdfId], snapshot);
|
||||
};
|
||||
},
|
||||
onError: (error, _variables, rollback) => {
|
||||
console.log("error", error);
|
||||
rollback?.();
|
||||
},
|
||||
onSettled: () => {
|
||||
return queryClient.invalidateQueries({
|
||||
queryKey: ["pitchBookKPI", pdfId],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Bearbeitung starten
|
||||
const startEditing = (value: string, index: string) => {
|
||||
setEditingIndex(index);
|
||||
setEditValue(value);
|
||||
};
|
||||
|
||||
// Bearbeitung beenden und Wert speichern
|
||||
const handleSave = async (index: string) => {
|
||||
// await updateKennzahl(rows[index].label, editValue);
|
||||
mutate(index);
|
||||
setEditingIndex("");
|
||||
};
|
||||
|
||||
// Tastatureingaben verarbeiten
|
||||
const handleKeyPress = (e: KeyboardEvent<HTMLDivElement>, index: string) => {
|
||||
if (e.key === "Enter") {
|
||||
handleSave(index);
|
||||
} else if (e.key === "Escape") {
|
||||
setEditingIndex("null");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<strong>Kennzahl</strong>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<strong>Wert</strong>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<strong>Seite</strong>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{SETTINGS.filter((setting) => setting.active)
|
||||
.sort((a, b) => a.position - b.position)
|
||||
.map((setting) => ({
|
||||
setting: setting,
|
||||
extractedValues: data[setting.name.toUpperCase()] || [],
|
||||
}))
|
||||
.map((row) => {
|
||||
let borderColor = "transparent";
|
||||
if (
|
||||
row.setting.mandatory &&
|
||||
(row.extractedValues.length === 0 ||
|
||||
row.extractedValues.at(0)?.entity === "")
|
||||
)
|
||||
borderColor = "red";
|
||||
else if (row.extractedValues.length > 1) borderColor = "#f6ed48";
|
||||
|
||||
return (
|
||||
<TableRow key={row.setting.name}>
|
||||
<TableCell>{row.setting.name}</TableCell>
|
||||
<TableCell
|
||||
onClick={() =>
|
||||
startEditing(
|
||||
row.extractedValues.at(0)?.entity || "",
|
||||
row.setting.name,
|
||||
)
|
||||
}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
border: `2px solid ${borderColor}`,
|
||||
borderRadius: 1,
|
||||
padding: "4px 8px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
width: "100%",
|
||||
cursor: "text",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 1,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{row.setting.mandatory &&
|
||||
row.extractedValues.length === 0 && (
|
||||
<ErrorOutlineIcon fontSize="small" color="error" />
|
||||
)}
|
||||
{editingIndex === row.setting.name ? (
|
||||
<TextField
|
||||
value={editValue}
|
||||
onChange={(e) => 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" }}
|
||||
/>
|
||||
) : (
|
||||
<span>
|
||||
{row.extractedValues.at(0)?.entity || "—"}
|
||||
</span>
|
||||
)}
|
||||
</Box>
|
||||
{row.extractedValues.length > 1 && (
|
||||
<IconButton
|
||||
aria-label="select"
|
||||
onClick={() =>
|
||||
navigate({
|
||||
to: "/extractedResult/$pitchBook/$kpi",
|
||||
params: {
|
||||
pitchBook: pdfId,
|
||||
kpi: row.setting.name,
|
||||
},
|
||||
})
|
||||
}
|
||||
>
|
||||
<SearchIcon
|
||||
fontSize="small"
|
||||
sx={{ color: "#f6ed48" }}
|
||||
/>
|
||||
</IconButton>
|
||||
)}
|
||||
{row.extractedValues.length <= 1 && (
|
||||
<EditIcon
|
||||
fontSize="small"
|
||||
sx={{ color: "#555", cursor: "pointer" }}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
startEditing(
|
||||
row.extractedValues.at(0)?.entity || "",
|
||||
row.setting.name,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Link
|
||||
component="button"
|
||||
onClick={() =>
|
||||
onPageClick?.(Number(row.extractedValues.at(0)?.page))
|
||||
}
|
||||
sx={{ cursor: "pointer" }}
|
||||
>
|
||||
{row.extractedValues.at(0)?.page}
|
||||
</Link>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ interface PDFViewerProps {
|
|||
currentPage?: number;
|
||||
}
|
||||
|
||||
export default function PDFViewer({ pitchBookId, currentPage }: PDFViewerProps) {
|
||||
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);
|
||||
|
|
@ -39,7 +42,7 @@ export default function PDFViewer({ pitchBookId, currentPage }: PDFViewerProps)
|
|||
if (currentPage && currentPage !== pageNumber) {
|
||||
setPageNumber(currentPage);
|
||||
}
|
||||
}, [currentPage]);
|
||||
}, [currentPage, pageNumber]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleProgress = (data: { id: number; progress: number }) => {
|
||||
|
|
@ -115,4 +118,4 @@ export default function PDFViewer({ pitchBookId, currentPage }: PDFViewerProps)
|
|||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { Route as ConfigImport } from './routes/config'
|
|||
import { Route as IndexImport } from './routes/index'
|
||||
import { Route as ExtractedResultPitchBookImport } from './routes/extractedResult.$pitchBook'
|
||||
import { Route as ConfigDetailKpiIdImport } from './routes/config-detail.$kpiId'
|
||||
import { Route as ExtractedResultPitchBookKpiImport } from './routes/extractedResult_.$pitchBook.$kpi'
|
||||
|
||||
// Create/Update Routes
|
||||
|
||||
|
|
@ -49,6 +50,13 @@ const ConfigDetailKpiIdRoute = ConfigDetailKpiIdImport.update({
|
|||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const ExtractedResultPitchBookKpiRoute =
|
||||
ExtractedResultPitchBookKpiImport.update({
|
||||
id: '/extractedResult_/$pitchBook/$kpi',
|
||||
path: '/extractedResult/$pitchBook/$kpi',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
// Populate the FileRoutesByPath interface
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
|
|
@ -88,6 +96,13 @@ declare module '@tanstack/react-router' {
|
|||
preLoaderRoute: typeof ExtractedResultPitchBookImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/extractedResult_/$pitchBook/$kpi': {
|
||||
id: '/extractedResult_/$pitchBook/$kpi'
|
||||
path: '/extractedResult/$pitchBook/$kpi'
|
||||
fullPath: '/extractedResult/$pitchBook/$kpi'
|
||||
preLoaderRoute: typeof ExtractedResultPitchBookKpiImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -99,6 +114,7 @@ export interface FileRoutesByFullPath {
|
|||
'/config-add': typeof ConfigAddRoute
|
||||
'/config-detail/$kpiId': typeof ConfigDetailKpiIdRoute
|
||||
'/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute
|
||||
'/extractedResult/$pitchBook/$kpi': typeof ExtractedResultPitchBookKpiRoute
|
||||
}
|
||||
|
||||
export interface FileRoutesByTo {
|
||||
|
|
@ -107,6 +123,7 @@ export interface FileRoutesByTo {
|
|||
'/config-add': typeof ConfigAddRoute
|
||||
'/config-detail/$kpiId': typeof ConfigDetailKpiIdRoute
|
||||
'/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute
|
||||
'/extractedResult/$pitchBook/$kpi': typeof ExtractedResultPitchBookKpiRoute
|
||||
}
|
||||
|
||||
export interface FileRoutesById {
|
||||
|
|
@ -116,6 +133,7 @@ export interface FileRoutesById {
|
|||
'/config-add': typeof ConfigAddRoute
|
||||
'/config-detail/$kpiId': typeof ConfigDetailKpiIdRoute
|
||||
'/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute
|
||||
'/extractedResult_/$pitchBook/$kpi': typeof ExtractedResultPitchBookKpiRoute
|
||||
}
|
||||
|
||||
export interface FileRouteTypes {
|
||||
|
|
@ -126,6 +144,7 @@ export interface FileRouteTypes {
|
|||
| '/config-add'
|
||||
| '/config-detail/$kpiId'
|
||||
| '/extractedResult/$pitchBook'
|
||||
| '/extractedResult/$pitchBook/$kpi'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to:
|
||||
| '/'
|
||||
|
|
@ -133,6 +152,7 @@ export interface FileRouteTypes {
|
|||
| '/config-add'
|
||||
| '/config-detail/$kpiId'
|
||||
| '/extractedResult/$pitchBook'
|
||||
| '/extractedResult/$pitchBook/$kpi'
|
||||
id:
|
||||
| '__root__'
|
||||
| '/'
|
||||
|
|
@ -140,6 +160,7 @@ export interface FileRouteTypes {
|
|||
| '/config-add'
|
||||
| '/config-detail/$kpiId'
|
||||
| '/extractedResult/$pitchBook'
|
||||
| '/extractedResult_/$pitchBook/$kpi'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
|
||||
|
|
@ -149,6 +170,7 @@ export interface RootRouteChildren {
|
|||
ConfigAddRoute: typeof ConfigAddRoute
|
||||
ConfigDetailKpiIdRoute: typeof ConfigDetailKpiIdRoute
|
||||
ExtractedResultPitchBookRoute: typeof ExtractedResultPitchBookRoute
|
||||
ExtractedResultPitchBookKpiRoute: typeof ExtractedResultPitchBookKpiRoute
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
|
|
@ -157,6 +179,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|||
ConfigAddRoute: ConfigAddRoute,
|
||||
ConfigDetailKpiIdRoute: ConfigDetailKpiIdRoute,
|
||||
ExtractedResultPitchBookRoute: ExtractedResultPitchBookRoute,
|
||||
ExtractedResultPitchBookKpiRoute: ExtractedResultPitchBookKpiRoute,
|
||||
}
|
||||
|
||||
export const routeTree = rootRoute
|
||||
|
|
@ -173,7 +196,8 @@ export const routeTree = rootRoute
|
|||
"/config",
|
||||
"/config-add",
|
||||
"/config-detail/$kpiId",
|
||||
"/extractedResult/$pitchBook"
|
||||
"/extractedResult/$pitchBook",
|
||||
"/extractedResult_/$pitchBook/$kpi"
|
||||
]
|
||||
},
|
||||
"/": {
|
||||
|
|
@ -190,6 +214,9 @@ export const routeTree = rootRoute
|
|||
},
|
||||
"/extractedResult/$pitchBook": {
|
||||
"filePath": "extractedResult.$pitchBook.tsx"
|
||||
},
|
||||
"/extractedResult_/$pitchBook/$kpi": {
|
||||
"filePath": "extractedResult_.$pitchBook.$kpi.tsx"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
import ContentPasteIcon from "@mui/icons-material/ContentPaste";
|
||||
import { Box, Button, Paper, Typography } from "@mui/material";
|
||||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
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";
|
||||
|
||||
export const Route = createFileRoute("/extractedResult/$pitchBook")({
|
||||
component: ExtractedResultsPage,
|
||||
loader: ({ context: { queryClient }, params: { pitchBook } }) =>
|
||||
queryClient.ensureQueryData(kpiQueryOptions(pitchBook)),
|
||||
});
|
||||
|
||||
function ExtractedResultsPage() {
|
||||
|
|
@ -21,6 +25,8 @@ function ExtractedResultsPage() {
|
|||
green: "#3fd942",
|
||||
}[status];
|
||||
|
||||
const { data: kpi } = useSuspenseQuery(kpiQueryOptions(pitchBook));
|
||||
|
||||
return (
|
||||
<Box p={4}>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
|
|
@ -60,7 +66,11 @@ function ExtractedResultsPage() {
|
|||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
<KennzahlenTable onPageClick={setCurrentPage} />
|
||||
<KennzahlenTable
|
||||
onPageClick={setCurrentPage}
|
||||
data={kpi}
|
||||
pdfId={pitchBook}
|
||||
/>
|
||||
</Paper>
|
||||
<Box
|
||||
display="flex"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
import { useSuspenseQuery } from "@tanstack/react-query";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { kpiQueryOptions } from "../util/query";
|
||||
|
||||
export const Route = createFileRoute("/extractedResult_/$pitchBook/$kpi")({
|
||||
component: RouteComponent,
|
||||
loader: ({ context: { queryClient }, params: { pitchBook } }) =>
|
||||
queryClient.ensureQueryData(kpiQueryOptions(pitchBook)),
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const { pitchBook, kpi } = Route.useParams();
|
||||
|
||||
const {
|
||||
data: { [kpi.toUpperCase()]: kpiValues },
|
||||
} = useSuspenseQuery(kpiQueryOptions(pitchBook));
|
||||
|
||||
return (
|
||||
<div>
|
||||
{kpiValues.map((e) => (
|
||||
<div key={`${e.entity}_${e.page}`}>
|
||||
{e.label}: {e.entity}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
export const fetchKPI = async (
|
||||
pitchBookId: string,
|
||||
): Promise<{
|
||||
[key: string]: {
|
||||
label: string;
|
||||
entity: string;
|
||||
page: number;
|
||||
status: string;
|
||||
source: string;
|
||||
}[];
|
||||
}> => {
|
||||
const response = await fetch(
|
||||
`http://localhost:5050/api/pitch_book/${pitchBookId}`,
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
return getKPI(data.kpi);
|
||||
};
|
||||
|
||||
export const fetchPutKPI = async (
|
||||
pitchBookId: number,
|
||||
kpi: {
|
||||
[key: string]: {
|
||||
label: string;
|
||||
entity: string;
|
||||
page: number;
|
||||
status: string;
|
||||
source: string;
|
||||
}[];
|
||||
},
|
||||
): Promise<{
|
||||
[key: string]: {
|
||||
label: string;
|
||||
entity: string;
|
||||
page: number;
|
||||
status: string;
|
||||
source: string;
|
||||
}[];
|
||||
}> => {
|
||||
const formData = new FormData();
|
||||
formData.append("kpi", JSON.stringify(flattenKPIArray(kpi)));
|
||||
|
||||
const response = await fetch(
|
||||
`http://localhost:5050/api/pitch_book/${pitchBookId}`,
|
||||
{
|
||||
method: "PUT",
|
||||
body: formData,
|
||||
},
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
return getKPI(data.kpi);
|
||||
};
|
||||
|
||||
const getKPI = (data: string) => {
|
||||
const kpi = JSON.parse(data) as {
|
||||
label: string;
|
||||
entity: string;
|
||||
page: number;
|
||||
status: string;
|
||||
source: string;
|
||||
}[];
|
||||
|
||||
const reducedKpi = kpi.reduce(
|
||||
(prev, curr) => {
|
||||
if (!prev[curr.label]) {
|
||||
prev[curr.label] = [];
|
||||
}
|
||||
prev[curr.label].push(curr);
|
||||
return prev;
|
||||
},
|
||||
{} as {
|
||||
[key: string]: {
|
||||
label: string;
|
||||
entity: string;
|
||||
page: number;
|
||||
status: string;
|
||||
source: string;
|
||||
}[];
|
||||
},
|
||||
);
|
||||
return reducedKpi;
|
||||
};
|
||||
|
||||
export const flattenKPIArray = (kpi: {
|
||||
[key: string]: {
|
||||
label: string;
|
||||
entity: string;
|
||||
page: number;
|
||||
status: string;
|
||||
source: string;
|
||||
}[];
|
||||
}) => {
|
||||
return Object.values(kpi).flat();
|
||||
};
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import { queryOptions } from "@tanstack/react-query";
|
||||
import { fetchKPI } from "./api";
|
||||
|
||||
export const kpiQueryOptions = (pitchBookId: string) =>
|
||||
queryOptions({
|
||||
queryKey: ["pitchBookKPI", pitchBookId],
|
||||
queryFn: () => fetchKPI(pitchBookId),
|
||||
});
|
||||
Loading…
Reference in New Issue