Merge pull request 'Progress - Frontend' (#48) from #16-progress into main
Reviewed-on: #48pull/50/head
commit
b9d7f425e5
|
|
@ -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"]
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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"})
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from flask_socketio import SocketIO
|
||||||
|
|
||||||
|
socketio = SocketIO(cors_allowed_origins="*")
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"indentStyle": "space"
|
"indentStyle": "tab"
|
||||||
},
|
},
|
||||||
"organizeImports": {
|
"organizeImports": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -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
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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");
|
||||||
Loading…
Reference in New Issue