add progress and file-upload to frontend

pull/48/head
Jaronim Pracht 2025-06-02 15:03:39 +02:00
parent ba191dd0a6
commit 9aa6c8be87
14 changed files with 445 additions and 302 deletions

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

@ -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,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

@ -30,7 +30,7 @@ services:
timeout: 5s timeout: 5s
retries: 5 retries: 5
ports: ports:
- 5000:5000 - 5050:5000
spacy: spacy:
build: build:

Binary file not shown.

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": "^9.2.1",
"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) {
console.log("File uploaded successfully");
const data = await response.json();
console.log(data);
setPageId(data.id);
setLoadingState(0);
!PROGRESS &&
navigate({
to: "/extractedResult/$pitchBook",
params: { pitchBook: data.id },
});
} else {
console.error("Failed to upload file");
}
}, [files, navigate]);
const onConnection = useCallback(() => {
console.log("connected");
}, []);
const onProgress = useCallback(
(progress: { id: number; progress: number }) => {
console.log("Progress:", progress);
console.log(pageId);
if (Number(pageId) === progress.id) {
setLoadingState(progress.progress);
if (progress.progress === 100) {
navigate({
to: "/extractedResult/$pitchBook",
params: { pitchBook: progress.id.toString() },
});
}
}
},
[pageId, navigate],
);
useEffect(() => {
socket.on("connect", onConnection);
socket.on("progress", onProgress);
return () => {
socket.off("connect", onConnection);
socket.off("progress", onProgress);
};
}, [onConnection, onProgress]);
return (
<>
{PROGRESS && (
<Backdrop
sx={(theme) => ({ color: "#fff", zIndex: theme.zIndex.drawer + 1 })}
open={pageId !== null && loadingState !== null}
> >
<Box <CircularProgressWithLabel
width="100%" color="inherit"
maxWidth="1300px" value={loadingState || 0}
display="flex" size={60}
justifyContent="flex-end" />
px={2} </Backdrop>
> )}
<IconButton onClick={() => navigate({ to: '/config' })}> <Box
<SettingsIcon fontSize="large"/> display="flex"
</IconButton> flexDirection="column"
</Box> alignItems="center"
<Paper justifyContent="center"
elevation={3} height="100vh"
sx={{ bgcolor="white"
width: 900, >
height: 500, <Box
backgroundColor: '#eeeeee', width="100%"
borderRadius: 4, maxWidth="1300px"
display: 'flex', display="flex"
justifyContent: 'center', justifyContent="flex-end"
alignItems: 'center', px={2}
}} >
> <IconButton onClick={() => navigate({ to: "/config" })}>
<Box sx={{ <SettingsIcon fontSize="large" />
height: '100%', </IconButton>
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={() => navigate({ to: '/extractedResult' })}
>
Kennzahlen extrahieren
</Button>
</Box> </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,99 @@
import { useEffect, useRef, useState } from "react";
import { Document, Page, pdfjs } from "react-pdf"; import { Document, Page, pdfjs } from "react-pdf";
import { useState, useRef, useEffect } from 'react'; 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 ArrowCircleLeftIcon from "@mui/icons-material/ArrowCircleLeft";
import { Box, IconButton } from '@mui/material'; import ArrowCircleRightIcon from "@mui/icons-material/ArrowCircleRight";
import ArrowCircleLeftIcon from '@mui/icons-material/ArrowCircleLeft'; import { Box, IconButton } from "@mui/material";
import ArrowCircleRightIcon from '@mui/icons-material/ArrowCircleRight';
import testPDF from '/example.pdf';
pdfjs.GlobalWorkerOptions.workerSrc = new URL( pdfjs.GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/build/pdf.worker.min.mjs", "pdfjs-dist/build/pdf.worker.min.mjs",
import.meta.url, import.meta.url,
).toString(); ).toString();
export default function PDFViewer() { interface PDFViewerProps {
const [numPages, setNumPages] = useState<number | null>(null); pitchBookId: string;
const [pageNumber, setPageNumber] = useState(1); }
const [containerWidth, setContainerWidth] = useState<number | null>(null);
const containerRef = useRef<HTMLDivElement>(null); 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);
const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => { const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
setNumPages(numPages); setNumPages(numPages);
};
useEffect(() => {
const updateWidth = () => {
if (containerRef.current) {
setContainerWidth(containerRef.current.offsetWidth);
}
}; };
useEffect(() => { updateWidth();
const updateWidth = () => { window.addEventListener("resize", updateWidth);
if (containerRef.current) { return () => window.removeEventListener("resize", updateWidth);
setContainerWidth(containerRef.current.offsetWidth); }, []);
}
};
updateWidth(); return (
window.addEventListener('resize', updateWidth); <Box
return () => window.removeEventListener('resize', updateWidth); display="flex"
}, []); flexDirection="column"
justifyContent="center"
return ( alignItems="center"
<Box width="100%"
display="flex" height="100%"
flexDirection="column" p={2}
justifyContent="center" >
alignItems="center" <Box
width="100%" ref={containerRef}
height="100%" sx={{
p={2} width: "100%",
maxHeight: "90vh",
overflow: "auto",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<Document
file={`http://localhost:5050/api/pitch_book/${pitchBookId}/download`}
onLoadSuccess={onDocumentLoadSuccess}
onLoadError={(error) =>
console.error("Es gab ein Fehler beim Laden des PDFs:", error)
}
onSourceError={(error) => console.error("Ungültige PDF:", error)}
> >
<Box {containerWidth && (
ref={containerRef} <Page pageNumber={pageNumber} width={containerWidth * 0.8} />
sx={{ )}
width: '100%', </Document>
maxHeight: '90vh', </Box>
overflow: 'auto', <Box
display: 'flex', mt={2}
justifyContent: 'center', display="flex"
alignItems: 'center', alignItems="center"
}} justifyContent="center"
> gap={1}
<Document file={testPDF} >
onLoadSuccess={onDocumentLoadSuccess} <IconButton
onLoadError={(error) => console.error('Es gab ein Fehler beim Laden des PDFs:', error)} disabled={pageNumber <= 1}
onSourceError={(error) => console.error('Ungültige PDF:', error)}> onClick={() => setPageNumber((p) => p - 1)}
{containerWidth && ( >
<Page <ArrowCircleLeftIcon fontSize="large" />
pageNumber={pageNumber} </IconButton>
width={containerWidth * 0.8} <span>
/> {pageNumber} / {numPages}
)} </span>
</Document> <IconButton
</Box> disabled={pageNumber >= (numPages || 1)}
<Box onClick={() => setPageNumber((p) => p + 1)}
mt={2} >
display="flex" <ArrowCircleRightIcon fontSize="large" />
alignItems="center" </IconButton>
justifyContent="center" </Box>
gap={1} </Box>
> );
<IconButton disabled={pageNumber <= 1} onClick={() => setPageNumber(p => p - 1)}> }
<ArrowCircleLeftIcon fontSize="large" />
</IconButton>
<span>{pageNumber} / {numPages}</span>
<IconButton
disabled={pageNumber >= (numPages || 1)}
onClick={() => setPageNumber(p => p + 1)}
>
<ArrowCircleRightIcon fontSize="large" />
</IconButton>
</Box>
</Box>
);
}

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 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",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Typography color="textSecondary">To-do: Table hierhin</Typography>
</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,101 +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';
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',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Typography color="textSecondary">To-do: Table hierhin</Typography>
</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");