Compare commits

..

No commits in common. "211bb9a9d16382ce6dd0e87269960681cfa0c209" and "3816831619362360371e2bf48ad426fb61cc084d" have entirely different histories.

8 changed files with 219 additions and 442 deletions

View File

@ -1,3 +1,6 @@
from flask_socketio import SocketIO from flask_socketio import SocketIO
socketio = SocketIO(cors_allowed_origins="*") socketio = SocketIO(
cors_allowed_origins=["http://localhost:8080", "http://localhost:3000"],
transports=["polling", "websocket"],
)

View File

@ -1,265 +1,211 @@
import EditIcon from "@mui/icons-material/Edit";
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
import SearchIcon from "@mui/icons-material/Search";
import { import {
Box, Table, TableBody, TableCell, TableContainer,
IconButton, TableHead, TableRow, Paper, Box,
Link, TextField, Link
Paper, } from '@mui/material';
Table, import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
TableBody, import SearchIcon from '@mui/icons-material/Search';
TableCell, import EditIcon from '@mui/icons-material/Edit';
TableContainer, import { useState, useEffect } from 'react';
TableHead, import type { KeyboardEvent } from 'react';
TableRow,
TextField, const API_BASE_URL = 'http://localhost:5050'; // Korrigierter Port für den Coordinator-Service
} from "@mui/material";
import { useMutation, useQueryClient } from "@tanstack/react-query"; interface Kennzahl {
import { useNavigate } from "@tanstack/react-router"; pdf_id: string;
import { useState } from "react"; label: string;
import type { KeyboardEvent } from "react"; value: string;
import { fetchPutKPI } from "../util/api"; page: number;
status: 'ok' | 'error' | 'warning';
const SETTINGS = [ }
{ name: "Rendite", position: 1, active: true, mandatory: true },
{ name: "Ausschüttungsrendite", position: 2, active: true, mandatory: true }, interface KennzahlenTableProps {
{ name: "Laufzeit", position: 3, active: true, mandatory: true }, onPageClick?: (page: number) => void;
{ name: "Länderallokation", position: 4, active: true, mandatory: true }, pdfId?: string; // Neue Prop für die PDF-ID
{ 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 }, // React-Komponente
]; export default function KennzahlenTable({ onPageClick, pdfId = 'example' }: KennzahlenTableProps) {
const [rows, setRows] = useState<Kennzahl[]>([]);
interface KennzahlenTableProps { const [editingIndex, setEditingIndex] = useState<number | null>(null);
onPageClick?: (page: number) => void; const [editValue, setEditValue] = useState('');
pdfId: string; // Neue Prop für die PDF-ID const [isLoading, setIsLoading] = useState(true);
data: { const [error, setError] = useState<string | null>(null);
[key: string]: {
label: string; // Initialisiere Beispieldaten
entity: string; const initializeData = async () => {
page: number; try {
status: string; const response = await fetch(`${API_BASE_URL}/api/kennzahlen/init`, {
source: string; method: 'POST'
}[]; });
}; if (!response.ok) {
} throw new Error('Fehler beim Initialisieren der Daten');
}
// React-Komponente // Lade die Daten nach der Initialisierung
export default function KennzahlenTable({ await fetchKennzahlen();
onPageClick, } catch (err) {
data, setError('Fehler beim Initialisieren der Daten');
pdfId, console.error('Fehler:', err);
}: KennzahlenTableProps) { }
const [editingIndex, setEditingIndex] = useState<string>(""); };
const [editValue, setEditValue] = useState("");
const navigate = useNavigate({ from: "/extractedResult/$pitchBook" }); // Lade Kennzahlen vom Backend
const fetchKennzahlen = async () => {
const queryClient = useQueryClient(); try {
const response = await fetch(`${API_BASE_URL}/api/kennzahlen?pdf_id=${pdfId}`);
const { mutate } = useMutation({ if (!response.ok) {
mutationFn: (id: string) => { throw new Error('Fehler beim Laden der Kennzahlen');
const key = id.toUpperCase(); }
const updatedData = { ...data }; const data = await response.json();
updatedData[key] = data[key]?.map((item) => ({ if (data.length === 0) {
...item, // Wenn keine Daten vorhanden sind, initialisiere Beispieldaten
entity: editValue, await initializeData();
})) || [{ label: key, entity: editValue }]; } else {
return fetchPutKPI(Number(pdfId), updatedData); setRows(data);
}, setError(null);
onMutate: async (id: string) => { }
await queryClient.cancelQueries({ } catch (err) {
queryKey: ["pitchBookKPI", pdfId], setError('Fehler beim Laden der Daten');
}); console.error('Fehler:', err);
} finally {
const snapshot = queryClient.getQueryData(["pitchBookKPI", pdfId]); setIsLoading(false);
}
const key = id.toUpperCase(); };
queryClient.setQueryData(["pitchBookKPI", pdfId], () => { // Lade Daten beim ersten Render oder wenn sich die PDF-ID ändert
const updatedData = { ...data }; useEffect(() => {
updatedData[key] = data[key]?.map((item) => ({ fetchKennzahlen();
...item, }, [pdfId]);
entity: editValue,
})) || [{ label: key, entity: editValue }]; // Funktion zum Senden der PUT-Anfrage
return updatedData; const updateKennzahl = async (label: string, value: string) => {
}); try {
const response = await fetch(`${API_BASE_URL}/api/kennzahlen/${label}?pdf_id=${pdfId}`, {
return () => { method: 'PUT',
queryClient.setQueryData(["pitchBookKPI", pdfId], snapshot); headers: {
}; 'Content-Type': 'application/json',
}, },
onError: (error, _variables, rollback) => { body: JSON.stringify({ value })
console.log("error", error); });
rollback?.();
}, if (!response.ok) {
onSettled: () => { throw new Error('Fehler beim Speichern der Kennzahl');
return queryClient.invalidateQueries({ }
queryKey: ["pitchBookKPI", pdfId],
}); // Lade die Daten neu nach dem Update
}, await fetchKennzahlen();
}); } catch (error) {
console.error('Fehler:', error);
// Bearbeitung starten setError('Fehler beim Speichern der Änderungen');
const startEditing = (value: string, index: string) => { }
setEditingIndex(index); };
setEditValue(value);
}; // Bearbeitung starten
const startEditing = (value: string, index: number) => {
// Bearbeitung beenden und Wert speichern setEditingIndex(index);
const handleSave = async (index: string) => { setEditValue(value);
// await updateKennzahl(rows[index].label, editValue); };
mutate(index);
setEditingIndex(""); // Bearbeitung beenden und Wert speichern
}; const handleSave = async (index: number) => {
await updateKennzahl(rows[index].label, editValue);
// Tastatureingaben verarbeiten setEditingIndex(null);
const handleKeyPress = (e: KeyboardEvent<HTMLDivElement>, index: string) => { };
if (e.key === "Enter") {
handleSave(index); // Tastatureingaben verarbeiten
} else if (e.key === "Escape") { const handleKeyPress = (e: KeyboardEvent<HTMLDivElement>, index: number) => {
setEditingIndex("null"); if (e.key === 'Enter') {
} handleSave(index);
}; } else if (e.key === 'Escape') {
setEditingIndex(null);
return ( }
<TableContainer component={Paper}> };
<Table>
<TableHead> if (isLoading) {
<TableRow> return <div>Lade Daten...</div>;
<TableCell> }
<strong>Kennzahl</strong>
</TableCell> if (error) {
<TableCell> return <Box sx={{ color: 'error.main' }}>{error}</Box>;
<strong>Wert</strong> }
</TableCell>
<TableCell> return (
<strong>Seite</strong> <TableContainer component={Paper}>
</TableCell> <Table>
</TableRow> <TableHead>
</TableHead> <TableRow>
<TableCell><strong>Kennzahl</strong></TableCell>
<TableBody> <TableCell><strong>Wert</strong></TableCell>
{SETTINGS.filter((setting) => setting.active) <TableCell><strong>Seite</strong></TableCell>
.sort((a, b) => a.position - b.position) </TableRow>
.map((setting) => ({ </TableHead>
setting: setting,
extractedValues: data[setting.name.toUpperCase()] || [], <TableBody>
})) {rows.map((row, index) => {
.map((row) => { let borderColor = 'transparent';
let borderColor = "transparent"; if (row.status === 'error') borderColor = 'red';
if ( else if (row.status === 'warning') borderColor = '#f6ed48';
row.setting.mandatory &&
(row.extractedValues.length === 0 || return (
row.extractedValues.at(0)?.entity === "") <TableRow key={index}>
) <TableCell>{row.label}</TableCell>
borderColor = "red"; <TableCell onClick={() => startEditing(row.value, index)}>
else if (row.extractedValues.length > 1) borderColor = "#f6ed48"; <Box
sx={{
return ( border: `2px solid ${borderColor}`,
<TableRow key={row.setting.name}> borderRadius: 1,
<TableCell>{row.setting.name}</TableCell> padding: '4px 8px',
<TableCell display: 'flex',
onClick={() => alignItems: 'center',
startEditing( justifyContent: 'space-between',
row.extractedValues.at(0)?.entity || "", width: '100%',
row.setting.name, cursor: 'text',
) }}
} >
> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, width: '100%' }}>
<Box {row.status === 'error' && <ErrorOutlineIcon fontSize="small" color="error" />}
sx={{ {row.status === 'warning' && <SearchIcon fontSize="small" sx={{ color: '#f6ed48' }} />}
border: `2px solid ${borderColor}`, {editingIndex === index ? (
borderRadius: 1, <TextField
padding: "4px 8px", value={editValue}
display: "flex", onChange={(e) => setEditValue(e.target.value)}
alignItems: "center", onKeyDown={(e) => handleKeyPress(e, index)}
justifyContent: "space-between", onBlur={() => handleSave(index)}
width: "100%", autoFocus
cursor: "text", size="small"
}} fullWidth
> variant="standard"
<Box sx={{ margin: '-8px 0' }}
sx={{ />
display: "flex", ) : (
alignItems: "center", <span>{row.value || '—'}</span>
gap: 1, )}
width: "100%", </Box>
}} <EditIcon
> fontSize="small"
{row.setting.mandatory && sx={{ color: '#555', cursor: 'pointer' }}
row.extractedValues.length === 0 && ( onClick={(e) => {
<ErrorOutlineIcon fontSize="small" color="error" /> e.stopPropagation();
)} startEditing(row.value, index);
{editingIndex === row.setting.name ? ( }}
<TextField />
value={editValue} </Box>
onChange={(e) => setEditValue(e.target.value)} </TableCell>
onKeyDown={(e) => <TableCell>
handleKeyPress(e, row.setting.name) <Link
} component="button"
onBlur={() => handleSave(row.setting.name)} onClick={() => onPageClick?.(row.page)}
autoFocus sx={{ cursor: 'pointer' }}
size="small" >
fullWidth {row.page}
variant="standard" </Link>
sx={{ margin: "-8px 0" }} </TableCell>
/> </TableRow>
) : ( );
<span> })}
{row.extractedValues.at(0)?.entity || "—"} </TableBody>
</span> </Table>
)} </TableContainer>
</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>
);
}

View File

@ -12,10 +12,7 @@ interface PDFViewerProps {
currentPage?: number; currentPage?: number;
} }
export default function PDFViewer({ export default function PDFViewer({ pitchBookId, currentPage }: PDFViewerProps) {
pitchBookId,
currentPage,
}: PDFViewerProps) {
const [numPages, setNumPages] = useState<number | null>(null); const [numPages, setNumPages] = useState<number | null>(null);
const [pageNumber, setPageNumber] = useState(currentPage || 1); const [pageNumber, setPageNumber] = useState(currentPage || 1);
const [containerWidth, setContainerWidth] = useState<number | null>(null); const [containerWidth, setContainerWidth] = useState<number | null>(null);
@ -42,7 +39,7 @@ export default function PDFViewer({
if (currentPage && currentPage !== pageNumber) { if (currentPage && currentPage !== pageNumber) {
setPageNumber(currentPage); setPageNumber(currentPage);
} }
}, [currentPage, pageNumber]); }, [currentPage]);
useEffect(() => { useEffect(() => {
const handleProgress = (data: { id: number; progress: number }) => { const handleProgress = (data: { id: number; progress: number }) => {
@ -118,4 +115,4 @@ export default function PDFViewer({
</Box> </Box>
</Box> </Box>
); );
} }

View File

@ -16,7 +16,6 @@ import { Route as ConfigImport } from './routes/config'
import { Route as IndexImport } from './routes/index' import { Route as IndexImport } from './routes/index'
import { Route as ExtractedResultPitchBookImport } from './routes/extractedResult.$pitchBook' import { Route as ExtractedResultPitchBookImport } from './routes/extractedResult.$pitchBook'
import { Route as ConfigDetailKpiIdImport } from './routes/config-detail.$kpiId' import { Route as ConfigDetailKpiIdImport } from './routes/config-detail.$kpiId'
import { Route as ExtractedResultPitchBookKpiImport } from './routes/extractedResult_.$pitchBook.$kpi'
// Create/Update Routes // Create/Update Routes
@ -50,13 +49,6 @@ const ConfigDetailKpiIdRoute = ConfigDetailKpiIdImport.update({
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any) } as any)
const ExtractedResultPitchBookKpiRoute =
ExtractedResultPitchBookKpiImport.update({
id: '/extractedResult_/$pitchBook/$kpi',
path: '/extractedResult/$pitchBook/$kpi',
getParentRoute: () => rootRoute,
} as any)
// Populate the FileRoutesByPath interface // Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {
@ -96,13 +88,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ExtractedResultPitchBookImport preLoaderRoute: typeof ExtractedResultPitchBookImport
parentRoute: typeof rootRoute parentRoute: typeof rootRoute
} }
'/extractedResult_/$pitchBook/$kpi': {
id: '/extractedResult_/$pitchBook/$kpi'
path: '/extractedResult/$pitchBook/$kpi'
fullPath: '/extractedResult/$pitchBook/$kpi'
preLoaderRoute: typeof ExtractedResultPitchBookKpiImport
parentRoute: typeof rootRoute
}
} }
} }
@ -114,7 +99,6 @@ export interface FileRoutesByFullPath {
'/config-add': typeof ConfigAddRoute '/config-add': typeof ConfigAddRoute
'/config-detail/$kpiId': typeof ConfigDetailKpiIdRoute '/config-detail/$kpiId': typeof ConfigDetailKpiIdRoute
'/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute '/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute
'/extractedResult/$pitchBook/$kpi': typeof ExtractedResultPitchBookKpiRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
@ -123,7 +107,6 @@ export interface FileRoutesByTo {
'/config-add': typeof ConfigAddRoute '/config-add': typeof ConfigAddRoute
'/config-detail/$kpiId': typeof ConfigDetailKpiIdRoute '/config-detail/$kpiId': typeof ConfigDetailKpiIdRoute
'/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute '/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute
'/extractedResult/$pitchBook/$kpi': typeof ExtractedResultPitchBookKpiRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
@ -133,7 +116,6 @@ export interface FileRoutesById {
'/config-add': typeof ConfigAddRoute '/config-add': typeof ConfigAddRoute
'/config-detail/$kpiId': typeof ConfigDetailKpiIdRoute '/config-detail/$kpiId': typeof ConfigDetailKpiIdRoute
'/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute '/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute
'/extractedResult_/$pitchBook/$kpi': typeof ExtractedResultPitchBookKpiRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
@ -144,7 +126,6 @@ export interface FileRouteTypes {
| '/config-add' | '/config-add'
| '/config-detail/$kpiId' | '/config-detail/$kpiId'
| '/extractedResult/$pitchBook' | '/extractedResult/$pitchBook'
| '/extractedResult/$pitchBook/$kpi'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: to:
| '/' | '/'
@ -152,7 +133,6 @@ export interface FileRouteTypes {
| '/config-add' | '/config-add'
| '/config-detail/$kpiId' | '/config-detail/$kpiId'
| '/extractedResult/$pitchBook' | '/extractedResult/$pitchBook'
| '/extractedResult/$pitchBook/$kpi'
id: id:
| '__root__' | '__root__'
| '/' | '/'
@ -160,7 +140,6 @@ export interface FileRouteTypes {
| '/config-add' | '/config-add'
| '/config-detail/$kpiId' | '/config-detail/$kpiId'
| '/extractedResult/$pitchBook' | '/extractedResult/$pitchBook'
| '/extractedResult_/$pitchBook/$kpi'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
@ -170,7 +149,6 @@ export interface RootRouteChildren {
ConfigAddRoute: typeof ConfigAddRoute ConfigAddRoute: typeof ConfigAddRoute
ConfigDetailKpiIdRoute: typeof ConfigDetailKpiIdRoute ConfigDetailKpiIdRoute: typeof ConfigDetailKpiIdRoute
ExtractedResultPitchBookRoute: typeof ExtractedResultPitchBookRoute ExtractedResultPitchBookRoute: typeof ExtractedResultPitchBookRoute
ExtractedResultPitchBookKpiRoute: typeof ExtractedResultPitchBookKpiRoute
} }
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
@ -179,7 +157,6 @@ const rootRouteChildren: RootRouteChildren = {
ConfigAddRoute: ConfigAddRoute, ConfigAddRoute: ConfigAddRoute,
ConfigDetailKpiIdRoute: ConfigDetailKpiIdRoute, ConfigDetailKpiIdRoute: ConfigDetailKpiIdRoute,
ExtractedResultPitchBookRoute: ExtractedResultPitchBookRoute, ExtractedResultPitchBookRoute: ExtractedResultPitchBookRoute,
ExtractedResultPitchBookKpiRoute: ExtractedResultPitchBookKpiRoute,
} }
export const routeTree = rootRoute export const routeTree = rootRoute
@ -196,8 +173,7 @@ export const routeTree = rootRoute
"/config", "/config",
"/config-add", "/config-add",
"/config-detail/$kpiId", "/config-detail/$kpiId",
"/extractedResult/$pitchBook", "/extractedResult/$pitchBook"
"/extractedResult_/$pitchBook/$kpi"
] ]
}, },
"/": { "/": {
@ -214,9 +190,6 @@ export const routeTree = rootRoute
}, },
"/extractedResult/$pitchBook": { "/extractedResult/$pitchBook": {
"filePath": "extractedResult.$pitchBook.tsx" "filePath": "extractedResult.$pitchBook.tsx"
},
"/extractedResult_/$pitchBook/$kpi": {
"filePath": "extractedResult_.$pitchBook.$kpi.tsx"
} }
} }
} }

View File

@ -1,16 +1,12 @@
import ContentPasteIcon from "@mui/icons-material/ContentPaste"; import ContentPasteIcon from "@mui/icons-material/ContentPaste";
import { Box, Button, Paper, Typography } from "@mui/material"; import { Box, Button, Paper, Typography } from "@mui/material";
import { useSuspenseQuery } from "@tanstack/react-query";
import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useState } from "react"; import { useState } from "react";
import KennzahlenTable from "../components/KennzahlenTable"; import KennzahlenTable from "../components/KennzahlenTable";
import PDFViewer from "../components/pdfViewer"; import PDFViewer from "../components/pdfViewer";
import { kpiQueryOptions } from "../util/query";
export const Route = createFileRoute("/extractedResult/$pitchBook")({ export const Route = createFileRoute("/extractedResult/$pitchBook")({
component: ExtractedResultsPage, component: ExtractedResultsPage,
loader: ({ context: { queryClient }, params: { pitchBook } }) =>
queryClient.ensureQueryData(kpiQueryOptions(pitchBook)),
}); });
function ExtractedResultsPage() { function ExtractedResultsPage() {
@ -25,8 +21,6 @@ function ExtractedResultsPage() {
green: "#3fd942", green: "#3fd942",
}[status]; }[status];
const { data: kpi } = useSuspenseQuery(kpiQueryOptions(pitchBook));
return ( return (
<Box p={4}> <Box p={4}>
<Box display="flex" alignItems="center" gap={3}> <Box display="flex" alignItems="center" gap={3}>
@ -66,11 +60,7 @@ function ExtractedResultsPage() {
overflow: "auto", overflow: "auto",
}} }}
> >
<KennzahlenTable <KennzahlenTable onPageClick={setCurrentPage} />
onPageClick={setCurrentPage}
data={kpi}
pdfId={pitchBook}
/>
</Paper> </Paper>
<Box <Box
display="flex" display="flex"

View File

@ -1,27 +0,0 @@
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>
);
}

View File

@ -1,97 +0,0 @@
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();
};

View File

@ -1,8 +0,0 @@
import { queryOptions } from "@tanstack/react-query";
import { fetchKPI } from "./api";
export const kpiQueryOptions = (pitchBookId: string) =>
queryOptions({
queryKey: ["pitchBookKPI", pitchBookId],
queryFn: () => fetchKPI(pitchBookId),
});