Merge pull request 'Progress - Frontend' (#48) from #16-progress into main

Reviewed-on: #48
pull/50/head
Anastasia Hanna Ougolnikova 2025-06-03 13:44:57 +02:00
commit b9d7f425e5
21 changed files with 493 additions and 7907 deletions

View File

@ -0,0 +1,21 @@
# 1. Python-Image verwenden
FROM python:3.11-alpine
# 2. Arbeitsverzeichnis im Container setzen
WORKDIR /app
# 3. production-style server mit gunicorn
RUN pip install gunicorn eventlet
# 4. requirements.txt kopieren und Pakete installieren
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 5. Quellcode kopieren (z.B. app.py)
COPY . .
ENV PYTHONUNBUFFERED=1
EXPOSE 5000
CMD ["gunicorn", "--worker-class", "eventlet", "-w", "1", "--bind", "0.0.0.0:5000", "app:app"]

View File

@ -1,10 +1,14 @@
from flask import Flask from flask import Flask
from flask_cors import CORS
import os import os
from dotenv import load_dotenv from dotenv import load_dotenv
from controller import register_routes from controller import register_routes
from model.database import init_db from model.database import init_db
from controller.socketIO import socketio
app = Flask(__name__) app = Flask(__name__)
CORS(app)
socketio.init_app(app)
load_dotenv() load_dotenv()
DATABASE_URL = os.getenv("DATABASE_URL") DATABASE_URL = os.getenv("DATABASE_URL")
@ -25,4 +29,4 @@ def health_check():
# für Docker wichtig: host='0.0.0.0' # für Docker wichtig: host='0.0.0.0'
if __name__ == "__main__": if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0") socketio.run(app,debug=True, host="0.0.0.0", port=5050)

View File

@ -1,9 +1,11 @@
from controller.spacy_contoller import spacy_controller from controller.spacy_contoller import spacy_controller
from controller.kpi_setting_controller import kpi_setting_controller from controller.kpi_setting_controller import kpi_setting_controller
from controller.pitch_book_controller import pitch_book_controller from controller.pitch_book_controller import pitch_book_controller
from controller.progress_controller import progress_controller
def register_routes(app): def register_routes(app):
app.register_blueprint(kpi_setting_controller) app.register_blueprint(kpi_setting_controller)
app.register_blueprint(pitch_book_controller) app.register_blueprint(pitch_book_controller)
app.register_blueprint(spacy_controller) app.register_blueprint(spacy_controller)
app.register_blueprint(progress_controller)

View File

@ -4,6 +4,7 @@ from model.pitch_book_model import PitchBookModel
from io import BytesIO from io import BytesIO
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import puremagic import puremagic
from controller.socketIO import socketio
pitch_book_controller = Blueprint("pitch_books", __name__, url_prefix="/api/pitch_book") pitch_book_controller = Blueprint("pitch_books", __name__, url_prefix="/api/pitch_book")
@ -54,6 +55,7 @@ def upload_file():
db.session.add(new_file) db.session.add(new_file)
db.session.commit() db.session.commit()
socketio.emit("progress", {"id": new_file.id, "progress": 0})
return jsonify(new_file.to_dict()), 201 return jsonify(new_file.to_dict()), 201
except Exception as e: except Exception as e:
print(e) print(e)
@ -81,6 +83,7 @@ def update_file(id):
print(e) print(e)
if "kpi" in request.form: if "kpi" in request.form:
socketio.emit("progress", {"id": id, "progress": 100})
file.kpi = request.form.get("kpi") file.kpi = request.form.get("kpi")
db.session.commit() db.session.commit()

View File

@ -0,0 +1,19 @@
from flask import Blueprint, request, jsonify
from controller.socketIO import socketio
progress_controller = Blueprint("progress", __name__, url_prefix="/api/progress")
@progress_controller.route("/", methods=["POST"])
def progress():
data = request.get_json()
if 'id' not in data or 'progress' not in data:
return jsonify({"error": "Missing required fields. [id, progress]"}), 400
if not isinstance(data['progress'], (int, float)) or data['progress'] < 0 or data['progress'] >= 100:
return jsonify({"error": "Invalid progress value"}), 400
socketio.emit("progress", {"id": data["id"], "progress": data["progress"]})
# Process the data and return a response
return jsonify({"message": "Progress updated"})

View File

@ -0,0 +1,3 @@
from flask_socketio import SocketIO
socketio = SocketIO(cors_allowed_origins="*")

View File

@ -1,3 +1,4 @@
bidict==0.23.1
black==25.1.0 black==25.1.0
blinker==1.9.0 blinker==1.9.0
cfgv==3.4.0 cfgv==3.4.0
@ -6,8 +7,11 @@ distlib==0.3.9
filelock==3.18.0 filelock==3.18.0
flake8==7.2.0 flake8==7.2.0
Flask==3.1.1 Flask==3.1.1
flask-cors==6.0.0
Flask-SocketIO==5.5.1
Flask-SQLAlchemy==3.1.1 Flask-SQLAlchemy==3.1.1
greenlet==3.2.2 greenlet==3.2.2
h11==0.16.0
identify==2.6.12 identify==2.6.12
itsdangerous==2.2.0 itsdangerous==2.2.0
Jinja2==3.1.6 Jinja2==3.1.6
@ -24,8 +28,12 @@ puremagic==1.29
pycodestyle==2.13.0 pycodestyle==2.13.0
pyflakes==3.3.2 pyflakes==3.3.2
python-dotenv==1.1.0 python-dotenv==1.1.0
python-engineio==4.12.1
python-socketio==5.13.0
PyYAML==6.0.2 PyYAML==6.0.2
simple-websocket==1.1.0
SQLAlchemy==2.0.41 SQLAlchemy==2.0.41
typing_extensions==4.13.2 typing_extensions==4.13.2
virtualenv==20.31.2 virtualenv==20.31.2
Werkzeug==3.1.3 Werkzeug==3.1.3
wsproto==1.2.0

View File

@ -19,11 +19,11 @@ services:
coordinator: coordinator:
build: build:
context: backend/coordinator context: backend/coordinator
dockerfile: ../../Dockerfile
env_file: env_file:
- .env - .env
depends_on: depends_on:
- db db:
condition: service_healthy
healthcheck: healthcheck:
test: wget --spider --no-verbose http://127.0.0.1:5000/health || exit 1 test: wget --spider --no-verbose http://127.0.0.1:5000/health || exit 1
interval: 10s interval: 10s

View File

@ -12,7 +12,7 @@
}, },
"formatter": { "formatter": {
"enabled": true, "enabled": true,
"indentStyle": "space" "indentStyle": "tab"
}, },
"organizeImports": { "organizeImports": {
"enabled": true "enabled": true

Binary file not shown.

View File

@ -20,4 +20,4 @@ server {
location = /50x.html { location = /50x.html {
root /usr/share/nginx/html; root /usr/share/nginx/html;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,8 @@
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-material-file-upload": "^0.0.4", "react-material-file-upload": "^0.0.4",
"react-pdf": "^9.2.1" "react-pdf": "^8.0.2",
"socket.io-client": "^4.8.1"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "1.9.4", "@biomejs/biome": "1.9.4",

View File

@ -0,0 +1,33 @@
import Box from "@mui/material/Box";
import CircularProgress, {
type CircularProgressProps,
} from "@mui/material/CircularProgress";
import Typography from "@mui/material/Typography";
export function CircularProgressWithLabel(
props: CircularProgressProps & { value: number },
) {
return (
<Box sx={{ position: "relative", display: "inline-flex" }}>
<CircularProgress {...props} />
<Box
sx={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: "absolute",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Typography
variant="subtitle1"
component="div"
sx={{ color: "inherit" }}
>{`${Math.round(props.value)}%`}</Typography>
</Box>
</Box>
);
}

View File

@ -1,100 +1,178 @@
import { useState } from 'react' import SettingsIcon from "@mui/icons-material/Settings";
import FileUpload from 'react-material-file-upload' import { Backdrop, Box, Button, IconButton, Paper } from "@mui/material";
import {Box, Button, IconButton, Paper} from '@mui/material' import { useNavigate } from "@tanstack/react-router";
import { useNavigate } from '@tanstack/react-router' import { useCallback, useEffect, useState } from "react";
import SettingsIcon from '@mui/icons-material/Settings'; import FileUpload from "react-material-file-upload";
import { socket } from "../socket";
import { CircularProgressWithLabel } from "./CircularProgressWithLabel";
const PROGRESS = false;
export default function UploadPage() { export default function UploadPage() {
const [files, setFiles] = useState<File[]>([]) const [files, setFiles] = useState<File[]>([]);
const fileTypes = ["pdf"]; const [pageId, setPageId] = useState<string | null>(null);
const navigate = useNavigate() const [loadingState, setLoadingState] = useState<number | null>(null);
const fileTypes = ["pdf"];
const navigate = useNavigate();
return ( const uploadFile = useCallback(async () => {
<Box const formData = new FormData();
display="flex" formData.append("file", files[0]);
flexDirection="column" const response = await fetch("http://localhost:5050/api/pitch_book", {
alignItems="center" method: "POST",
justifyContent="center" body: formData,
height="100vh" });
bgcolor="white"
> if (response.ok) {
<Box console.log("File uploaded successfully");
width="100%" const data = await response.json();
maxWidth="1300px" console.log(data);
display="flex" setPageId(data.id);
justifyContent="flex-end" setLoadingState(0);
px={2}
> !PROGRESS &&
<IconButton onClick={() => navigate({ to: '/config' })}> navigate({
<SettingsIcon fontSize="large"/> to: "/extractedResult/$pitchBook",
</IconButton> params: { pitchBook: data.id },
</Box> });
<Paper } else {
elevation={3} console.error("Failed to upload file");
sx={{ }
width: 900, }, [files, navigate]);
height: 500,
backgroundColor: '#eeeeee', const onConnection = useCallback(() => {
borderRadius: 4, console.log("connected");
display: 'flex', }, []);
justifyContent: 'center',
alignItems: 'center', const onProgress = useCallback(
}} (progress: { id: number; progress: number }) => {
> console.log("Progress:", progress);
<Box sx={{ console.log(pageId);
height: '100%', if (Number(pageId) === progress.id) {
width: '100%', setLoadingState(progress.progress);
maxWidth: '100%',
margin: '0px', if (progress.progress === 100) {
padding: '0px', navigate({
'& .MuiBox-root': { to: "/extractedResult/$pitchBook",
display: 'flex', params: { pitchBook: progress.id.toString() },
flexDirection: 'column', });
alignItems: 'center', }
justifyContent: 'center', }
border: 'none', },
textAlign: 'center', [pageId, navigate],
}, );
}}>
<FileUpload useEffect(() => {
value={files} socket.on("connect", onConnection);
onChange={setFiles} socket.on("progress", onProgress);
accept={`.${fileTypes.join(', .')}`} return () => {
title="Hier Dokument hinziehen" socket.off("connect", onConnection);
buttonText="Datei auswählen" socket.off("progress", onProgress);
sx={{ };
height: '100%', }, [onConnection, onProgress]);
width: '100%',
padding: '0px', return (
'& svg': { <>
color: '#9e9e9e', {PROGRESS && (
}, <Backdrop
'& .MuiOutlinedInput-notchedOutline': { sx={(theme) => ({ color: "#fff", zIndex: theme.zIndex.drawer + 1 })}
border: 'none', open={pageId !== null && loadingState !== null}
}, >
'& .MuiButton-root': { <CircularProgressWithLabel
backgroundColor: '#9e9e9e', color="inherit"
}, value={loadingState || 0}
'& .MuiTypography-root': { size={60}
fontSize: '1.25rem', />
fontWeight: 500, </Backdrop>
marginBottom: 1, )}
}, <Box
}} display="flex"
/> flexDirection="column"
</Box> alignItems="center"
</Paper> justifyContent="center"
<Button height="100vh"
variant="contained" bgcolor="white"
sx={{ >
mt: 4, <Box
backgroundColor: '#383838', width="100%"
}} maxWidth="1300px"
disabled={files.length === 0} display="flex"
onClick={() => navigate({ to: '/extractedResult' })} justifyContent="flex-end"
> px={2}
Kennzahlen extrahieren >
</Button> <IconButton onClick={() => navigate({ to: "/config" })}>
</Box> <SettingsIcon fontSize="large" />
) </IconButton>
} </Box>
<Paper
elevation={3}
sx={{
width: 900,
height: 500,
backgroundColor: "#eeeeee",
borderRadius: 4,
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<Box
sx={{
height: "100%",
width: "100%",
maxWidth: "100%",
margin: "0px",
padding: "0px",
"& .MuiBox-root": {
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
border: "none",
textAlign: "center",
},
}}
>
<FileUpload
value={files}
onChange={setFiles}
accept={`.${fileTypes.join(", .")}`}
title="Hier Dokument hinziehen"
buttonText="Datei auswählen"
sx={{
height: "100%",
width: "100%",
padding: "0px",
"& svg": {
color: "#9e9e9e",
},
"& .MuiOutlinedInput-notchedOutline": {
border: "none",
},
"& .MuiButton-root": {
backgroundColor: "#9e9e9e",
},
"& .MuiTypography-root": {
fontSize: "1.25rem",
fontWeight: 500,
marginBottom: 1,
},
}}
/>
</Box>
</Paper>
<Button
variant="contained"
sx={{
mt: 4,
backgroundColor: "#383838",
}}
disabled={files.length === 0}
onClick={uploadFile}
>
Kennzahlen extrahieren
</Button>
</Box>
</>
);
}

View File

@ -1,91 +1,93 @@
import { Document, Page, pdfjs } from "react-pdf"; import { useEffect, useRef, useState } from "react";
import { useState, useRef, useEffect } from 'react'; import { Document, Page } from "react-pdf";
import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; import "react-pdf/dist/esm/Page/AnnotationLayer.css";
import 'react-pdf/dist/esm/Page/TextLayer.css'; import "react-pdf/dist/esm/Page/TextLayer.css";
import { Box, IconButton } from '@mui/material'; import ArrowCircleLeftIcon from "@mui/icons-material/ArrowCircleLeft";
import ArrowCircleLeftIcon from '@mui/icons-material/ArrowCircleLeft'; import ArrowCircleRightIcon from "@mui/icons-material/ArrowCircleRight";
import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight'; import { Box, IconButton } from "@mui/material";
import testPDF from '/example.pdf';
pdfjs.GlobalWorkerOptions.workerSrc = new URL( interface PDFViewerProps {
"pdfjs-dist/build/pdf.worker.min.mjs", pitchBookId: string;
import.meta.url, }
).toString(); export default function PDFViewer({ pitchBookId }: PDFViewerProps) {
const [numPages, setNumPages] = useState<number | null>(null);
const [pageNumber, setPageNumber] = useState(1);
const [containerWidth, setContainerWidth] = useState<number | null>(null);
const containerRef = useRef<HTMLDivElement>(null);
export default function PDFViewer() { const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
const [numPages, setNumPages] = useState<number | null>(null); setNumPages(numPages);
const [pageNumber, setPageNumber] = useState(1); };
const [containerWidth, setContainerWidth] = useState<number | null>(null);
const containerRef = useRef<HTMLDivElement>(null); useEffect(() => {
const updateWidth = () => {
if (containerRef.current) {
setContainerWidth(containerRef.current.offsetWidth);
}
};
const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => { updateWidth();
setNumPages(numPages); window.addEventListener("resize", updateWidth);
}; return () => window.removeEventListener("resize", updateWidth);
}, []);
useEffect(() => { return (
const updateWidth = () => { <Box
if (containerRef.current) { display="flex"
setContainerWidth(containerRef.current.offsetWidth); flexDirection="column"
} justifyContent="center"
}; alignItems="center"
width="100%"
updateWidth(); height="100%"
window.addEventListener('resize', updateWidth); p={2}
return () => window.removeEventListener('resize', updateWidth); >
}, []); <Box
ref={containerRef}
return ( sx={{
<Box width: "100%",
display="flex" maxHeight: "90vh",
flexDirection="column" overflow: "auto",
justifyContent="center" display: "flex",
alignItems="center" justifyContent: "center",
width="100%" alignItems: "center",
height="100%" }}
p={2} >
> <Document
<Box file={`http://localhost:5050/api/pitch_book/${pitchBookId}/download`}
ref={containerRef} onLoadSuccess={onDocumentLoadSuccess}
sx={{ onLoadError={(error) =>
width: '100%', console.error("Es gab ein Fehler beim Laden des PDFs:", error)
maxHeight: '90vh', }
overflow: 'auto', onSourceError={(error) => console.error("Ungültige PDF:", error)}
display: 'flex', >
justifyContent: 'center', {containerWidth && (
alignItems: 'center', <Page pageNumber={pageNumber} width={containerWidth * 0.8} />
}} )}
> </Document>
<Document file={testPDF} </Box>
onLoadSuccess={onDocumentLoadSuccess} <Box
onLoadError={(error) => console.error('Es gab ein Fehler beim Laden des PDFs:', error)} mt={2}
onSourceError={(error) => console.error('Ungültige PDF:', error)}> display="flex"
{containerWidth && ( alignItems="center"
<Page justifyContent="center"
pageNumber={pageNumber} gap={1}
width={containerWidth * 0.8} >
/> <IconButton
)} disabled={pageNumber <= 1}
</Document> onClick={() => setPageNumber((p) => p - 1)}
</Box> >
<Box <ArrowCircleLeftIcon fontSize="large" />
mt={2} </IconButton>
display="flex" <span>
alignItems="center" {pageNumber} / {numPages}
justifyContent="center" </span>
gap={1} <IconButton
> disabled={pageNumber >= (numPages || 1)}
<IconButton disabled={pageNumber <= 1} onClick={() => setPageNumber(p => p - 1)}> onClick={() => setPageNumber((p) => p + 1)}
<ArrowCircleLeftIcon fontSize="large" /> >
</IconButton> <ArrowCircleRightIcon fontSize="large" />
<span>{pageNumber} / {numPages}</span> </IconButton>
<IconButton </Box>
disabled={pageNumber >= (numPages || 1)} </Box>
onClick={() => setPageNumber(p => p + 1)} );
> }
<ArrowCircleRightIcon fontSize="large" />
</IconButton>
</Box>
</Box>
);
}

View File

@ -34,9 +34,8 @@ declare module "@tanstack/react-router" {
} }
} }
// Initialize PDF.js worker
pdfjs.GlobalWorkerOptions.workerSrc = new URL( pdfjs.GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/build/pdf.worker.min.mjs", "pdfjs-dist/build/pdf.worker.min.js",
import.meta.url, import.meta.url,
).toString(); ).toString();

View File

@ -11,18 +11,12 @@
// Import Routes // Import Routes
import { Route as rootRoute } from './routes/__root' import { Route as rootRoute } from './routes/__root'
import { Route as ExtractedResultImport } from './routes/extractedResult'
import { Route as ConfigImport } from './routes/config' 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'
// Create/Update Routes // Create/Update Routes
const ExtractedResultRoute = ExtractedResultImport.update({
id: '/extractedResult',
path: '/extractedResult',
getParentRoute: () => rootRoute,
} as any)
const ConfigRoute = ConfigImport.update({ const ConfigRoute = ConfigImport.update({
id: '/config', id: '/config',
path: '/config', path: '/config',
@ -35,6 +29,12 @@ const IndexRoute = IndexImport.update({
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
} as any) } as any)
const ExtractedResultPitchBookRoute = ExtractedResultPitchBookImport.update({
id: '/extractedResult/$pitchBook',
path: '/extractedResult/$pitchBook',
getParentRoute: () => rootRoute,
} as any)
// Populate the FileRoutesByPath interface // Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {
@ -53,11 +53,11 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ConfigImport preLoaderRoute: typeof ConfigImport
parentRoute: typeof rootRoute parentRoute: typeof rootRoute
} }
'/extractedResult': { '/extractedResult/$pitchBook': {
id: '/extractedResult' id: '/extractedResult/$pitchBook'
path: '/extractedResult' path: '/extractedResult/$pitchBook'
fullPath: '/extractedResult' fullPath: '/extractedResult/$pitchBook'
preLoaderRoute: typeof ExtractedResultImport preLoaderRoute: typeof ExtractedResultPitchBookImport
parentRoute: typeof rootRoute parentRoute: typeof rootRoute
} }
} }
@ -68,41 +68,41 @@ declare module '@tanstack/react-router' {
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof IndexRoute
'/config': typeof ConfigRoute '/config': typeof ConfigRoute
'/extractedResult': typeof ExtractedResultRoute '/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute '/': typeof IndexRoute
'/config': typeof ConfigRoute '/config': typeof ConfigRoute
'/extractedResult': typeof ExtractedResultRoute '/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRoute __root__: typeof rootRoute
'/': typeof IndexRoute '/': typeof IndexRoute
'/config': typeof ConfigRoute '/config': typeof ConfigRoute
'/extractedResult': typeof ExtractedResultRoute '/extractedResult/$pitchBook': typeof ExtractedResultPitchBookRoute
} }
export interface FileRouteTypes { export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/' | '/config' | '/extractedResult' fullPaths: '/' | '/config' | '/extractedResult/$pitchBook'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: '/' | '/config' | '/extractedResult' to: '/' | '/config' | '/extractedResult/$pitchBook'
id: '__root__' | '/' | '/config' | '/extractedResult' id: '__root__' | '/' | '/config' | '/extractedResult/$pitchBook'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute IndexRoute: typeof IndexRoute
ConfigRoute: typeof ConfigRoute ConfigRoute: typeof ConfigRoute
ExtractedResultRoute: typeof ExtractedResultRoute ExtractedResultPitchBookRoute: typeof ExtractedResultPitchBookRoute
} }
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, IndexRoute: IndexRoute,
ConfigRoute: ConfigRoute, ConfigRoute: ConfigRoute,
ExtractedResultRoute: ExtractedResultRoute, ExtractedResultPitchBookRoute: ExtractedResultPitchBookRoute,
} }
export const routeTree = rootRoute export const routeTree = rootRoute
@ -117,7 +117,7 @@ export const routeTree = rootRoute
"children": [ "children": [
"/", "/",
"/config", "/config",
"/extractedResult" "/extractedResult/$pitchBook"
] ]
}, },
"/": { "/": {
@ -126,8 +126,8 @@ export const routeTree = rootRoute
"/config": { "/config": {
"filePath": "config.tsx" "filePath": "config.tsx"
}, },
"/extractedResult": { "/extractedResult/$pitchBook": {
"filePath": "extractedResult.tsx" "filePath": "extractedResult.$pitchBook.tsx"
} }
} }
} }

View File

@ -0,0 +1,100 @@
import ContentPasteIcon from "@mui/icons-material/ContentPaste";
import { Box, Button, Paper, Typography } from "@mui/material";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import KennzahlenTable from "../components/KennzahlenTable";
import PDFViewer from "../components/pdfViewer";
export const Route = createFileRoute("/extractedResult/$pitchBook")({
component: ExtractedResultsPage,
});
function ExtractedResultsPage() {
const { pitchBook } = Route.useParams();
const navigate = useNavigate();
const status: "green" | "yellow" | "red" = "red";
const statusColor = {
red: "#f43131",
yellow: "#f6ed48",
green: "#3fd942",
}[status];
return (
<Box p={4}>
<Box display="flex" alignItems="center" gap={3}>
<Box
sx={{
width: 45,
height: 45,
borderRadius: "50%",
backgroundColor: statusColor,
top: 32,
left: 32,
}}
/>
<Typography variant="h5" gutterBottom>
Kennzahlen extrahiert aus: <br />
<strong>FONDSNAME: TODO</strong>
</Typography>
</Box>
<Box
display="flex"
gap={4}
sx={{
width: "100vw",
maxWidth: "100%",
height: "80vh",
mt: 4,
}}
>
<Paper
elevation={2}
sx={{
width: "45%",
height: "100%",
borderRadius: 2,
backgroundColor: "#eeeeee",
padding: 2,
overflow: "auto",
}}
>
<KennzahlenTable />
</Paper>
<Box
display="flex"
flexDirection="column"
justifyContent="space-between"
gap={5}
sx={{ width: "55%", height: "95%" }}
>
<Paper
elevation={2}
sx={{
height: "100%",
borderRadius: 2,
backgroundColor: "#eeeeee",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<PDFViewer pitchBookId={pitchBook} />
</Paper>
<Box mt={2} display="flex" justifyContent="flex-end" gap={2}>
<Button variant="contained" sx={{ backgroundColor: "#383838" }}>
<ContentPasteIcon sx={{ fontSize: 18, mr: 1 }} />
Kennzahlenzeile kopieren
</Button>
<Button
variant="contained"
sx={{ backgroundColor: "#383838" }}
onClick={() => navigate({ to: "/" })}
>
Neu hochladen
</Button>
</Box>
</Box>
</Box>
</Box>
);
}

View File

@ -1,103 +0,0 @@
import { Box, Paper, Typography, Button } from '@mui/material';
import {createFileRoute, useNavigate} from '@tanstack/react-router';
import PDFViewer from '../components/pdfViewer';
import ContentPasteIcon from '@mui/icons-material/ContentPaste';
import KennzahlenTable from "../components/KennzahlenTable";
export const Route = createFileRoute('/extractedResult')({
component: ExtractedResultsPage,
});
function ExtractedResultsPage() {
const navigate = useNavigate();
const status: 'green' | 'yellow' | 'red' = 'red';
const statusColor = {
red: '#f43131',
yellow: '#f6ed48',
green: '#3fd942',
}[status];
return (
<Box p={4}>
<Box display="flex" alignItems="center" gap={3}>
<Box
sx={{
width: 45,
height: 45,
borderRadius: '50%',
backgroundColor: statusColor,
top: 32,
left: 32,
}}
/>
<Typography variant="h5" gutterBottom>
Kennzahlen extrahiert aus: <br/><strong>FONDSNAME: TODO</strong>
</Typography>
</Box>
<Box
display="flex"
gap={4}
sx={{
width: '100vw',
maxWidth: '100%',
height: '80vh',
mt: 4,
}}
>
<Paper
elevation={2}
sx={{
width: '45%',
height: '100%',
borderRadius: 2,
backgroundColor: '#eeeeee',
padding: 2, // Etwas Abstand innen
overflow: 'auto', // Scrollen falls Tabelle zu lang
}}
>
<KennzahlenTable />
</Paper>
<Box
display="flex"
flexDirection="column"
justifyContent="space-between"
gap={5}
sx={{ width: '55%', height: '95%' }}
>
<Paper
elevation={2}
sx={{
height: '100%',
borderRadius: 2,
backgroundColor: '#eeeeee',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<PDFViewer/>
</Paper>
<Box mt={2} display="flex" justifyContent="flex-end" gap={2}>
<Button
variant="contained"
sx={{ backgroundColor: '#383838' }}
>
<ContentPasteIcon sx={{ fontSize: 18, mr: 1 }} />
Kennzahlenzeile kopieren
</Button>
<Button
variant="contained"
sx={{ backgroundColor: '#383838' }}
onClick={() => navigate({ to: '/' })}
>
Neu hochladen
</Button>
</Box>
</Box>
</Box>
</Box>
);
}

View File

@ -0,0 +1,6 @@
import { io } from "socket.io-client";
// "undefined" means the URL will be computed from the `window.location` object
// const URL = process.env.NODE_ENV === 'production' ? undefined : 'http://localhost:4000';
export const socket = io("http://localhost:5050");