Compare commits

...

5 Commits

9 changed files with 129 additions and 60 deletions

View File

@ -3,15 +3,14 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico?v=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta <meta
name="description" name="description"
content="Web site created using create-tsrouter-app" content="Web site created using create-tsrouter-app"
/> />
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<title>Create TanStack App - frontend</title> <title>Pitchbook Extractor</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -184,10 +184,10 @@ export default function KennzahlenTable({
<Table> <Table>
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell width="25%"> <TableCell width="30%">
<strong>Kennzahl</strong> <strong>Kennzahl</strong>
</TableCell> </TableCell>
<TableCell width="60%"> <TableCell width="55%">
<strong>Wert</strong> <strong>Wert</strong>
</TableCell> </TableCell>
<TableCell align="center" width="15%"> <TableCell align="center" width="15%">
@ -226,7 +226,11 @@ export default function KennzahlenTable({
return ( return (
<TableRow key={row.setting.name}> <TableRow key={row.setting.name}>
<TableCell>{row.setting.name}</TableCell> <TableCell>{row.setting.name}
{row.setting.mandatory && (
<span> *</span>
)}
</TableCell>
<TableCell <TableCell
onClick={() => { onClick={() => {
// Only allow inline editing for non-multiple value cells // Only allow inline editing for non-multiple value cells

View File

@ -1,11 +1,12 @@
import SettingsIcon from "@mui/icons-material/Settings"; import SettingsIcon from "@mui/icons-material/Settings";
import { Backdrop, Box, Button, IconButton, Paper } from "@mui/material"; import { Backdrop, Box, Button, IconButton, Paper, Typography } from "@mui/material";
import { useNavigate, useRouter } from "@tanstack/react-router"; import { useNavigate, useRouter } from "@tanstack/react-router";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import FileUpload from "react-material-file-upload"; import FileUpload from "react-material-file-upload";
import { socket } from "../socket"; import { socket } from "../socket";
import { API_HOST } from "../util/api"; import { API_HOST } from "../util/api";
import { CircularProgressWithLabel } from "./CircularProgressWithLabel"; import { CircularProgressWithLabel } from "./CircularProgressWithLabel";
import DekaLogo from "../assets/Deka_logo.png";
export default function UploadPage() { export default function UploadPage() {
const [files, setFiles] = useState<File[]>([]); const [files, setFiles] = useState<File[]>([]);
@ -87,26 +88,50 @@ export default function UploadPage() {
display="flex" display="flex"
flexDirection="column" flexDirection="column"
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="flex-start"
height="100vh" height="100vh"
bgcolor="white" bgcolor="white"
pt={3}
> >
<Box <Box
width="100%" width="100%"
maxWidth="1300px"
display="flex" display="flex"
justifyContent="flex-end" justifyContent="space-between"
px={2} alignItems="center"
px={8}
py={5}
> >
<Box sx={{ display: "flex", alignItems: "center" }}>
<img
src={DekaLogo}
alt="Company Logo"
style={{ height: "40px", width: "auto" }}
/>
</Box>
<IconButton onClick={() => navigate({ to: "/config" })}> <IconButton onClick={() => navigate({ to: "/config" })}>
<SettingsIcon fontSize="large" /> <SettingsIcon fontSize="large" />
</IconButton> </IconButton>
</Box> </Box>
<Typography
variant="h4"
component="h1"
sx={{
fontWeight: "bold",
color: "#383838",
marginBottom: 12,
marginTop: 6,
}}
>
Pitchbook Extractor
</Typography>
<Paper <Paper
elevation={3} elevation={3}
sx={{ sx={{
width: 900, width: 800,
height: 500, height: 400,
backgroundColor: "#eeeeee", backgroundColor: "#eeeeee",
borderRadius: 4, borderRadius: 4,
display: "flex", display: "flex",
@ -187,4 +212,4 @@ export default function UploadPage() {
</Box> </Box>
</> </>
); );
} }

View File

@ -95,53 +95,78 @@ export default function PDFViewer({
useEffect(() => { useEffect(() => {
const tmpPos: string[] = []; const tmpPos: string[] = [];
const tmpPosHighlight: string[] = []; const tmpPosHighlight: string[] = [];
const textItems = textContent.filter(
(e) => e.text !== "" && e.text !== " ",
);
textItems.forEach((e, i) => { if (textContent.length === 0) {
for (const s of highlight setPosHighlight([]);
.filter((h) => h.page === pageNumber) setPosHighlightFocus([]);
.map((h) => h.text)) { return;
if (s.split(" ")[0] === e.text) { }
if ( const findTextPositions = (searchText: string): number[] => {
s.split(" ").reduce((prev, curr, j) => { const positions: number[] = [];
return prev && curr === textItems[i + j].text; const normalizedSearch = searchText.toLowerCase().trim();
}, true)
) { textContent.forEach((item, index) => {
for ( if (item.text.toLowerCase().trim() === normalizedSearch) {
let k = textItems[i].i; positions.push(index);
k < textItems[i + s.split(" ").length]?.i || }
k < textItems[i + s.split(" ").length - 1]?.i; });
k++
if (positions.length === 0) {
let cumulativeText = '';
const textBoundaries: { start: number; end: number; index: number }[] = [];
textContent.forEach((item, index) => {
const start = cumulativeText.length;
cumulativeText += item.text;
const end = cumulativeText.length;
textBoundaries.push({ start, end, index });
});
const lowerCumulative = cumulativeText.toLowerCase();
let searchIndex = lowerCumulative.indexOf(normalizedSearch);
while (searchIndex !== -1) {
const endIndex = searchIndex + normalizedSearch.length;
textBoundaries.forEach(boundary => {
if (
(boundary.start <= searchIndex && searchIndex < boundary.end) || // Search starts in this item
(boundary.start < endIndex && endIndex <= boundary.end) || // Search ends in this item
(searchIndex <= boundary.start && boundary.end <= endIndex) // This item is completely within search
) { ) {
tmpPos.push(textContent[k].posKey); if (!positions.includes(boundary.index)) {
positions.push(boundary.index);
}
} }
} });
searchIndex = lowerCumulative.indexOf(normalizedSearch, searchIndex + 1);
} }
} }
return positions.sort((a, b) => a - b);
if (focusHighlight?.page === pageNumber) { };
if (focusHighlight.text.split(" ")[0] === e.text) { highlight
if ( .filter(h => h.page === pageNumber)
focusHighlight.text.split(" ").reduce((prev, curr, j) => { .forEach(highlightItem => {
return prev && curr === textItems[i + j].text; const positions = findTextPositions(highlightItem.text);
}, true) positions.forEach(pos => {
) { if (pos >= 0 && pos < textContent.length) {
for ( tmpPos.push(textContent[pos].posKey);
let k = textItems[i].i;
k < textItems[i + focusHighlight.text.split(" ").length]?.i ||
k < textItems[i + focusHighlight.text.split(" ").length - 1]?.i;
k++
) {
tmpPosHighlight.push(textContent[k].posKey);
}
} }
});
});
if (focusHighlight?.page === pageNumber && focusHighlight.text) {
const positions = findTextPositions(focusHighlight.text);
positions.forEach(pos => {
if (pos >= 0 && pos < textContent.length) {
tmpPosHighlight.push(textContent[pos].posKey);
} }
} });
}); }
setPosHighlight(tmpPos);
setPosHighlightFocus(tmpPosHighlight); setPosHighlight([...new Set(tmpPos)]);
setPosHighlightFocus([...new Set(tmpPosHighlight)]);
}, [highlight, focusHighlight, pageNumber, textContent]); }, [highlight, focusHighlight, pageNumber, textContent]);
const onGetTextSuccess: OnGetTextSuccess = useCallback((fullText) => { const onGetTextSuccess: OnGetTextSuccess = useCallback((fullText) => {

View File

@ -68,6 +68,7 @@ function ExtractedResultsPage() {
const [customValue, setCustomValue] = useState(""); const [customValue, setCustomValue] = useState("");
const [customPage, setCustomPage] = useState(""); const [customPage, setCustomPage] = useState("");
const [editingCustomPage, setEditingCustomPage] = useState(false); const [editingCustomPage, setEditingCustomPage] = useState(false);
const [focusHighlightOverride, setFocusHighlightOverride] = useState<{ page: number; text: string } | null>(null);
const originalValue = kpiValues[0]?.entity || ""; const originalValue = kpiValues[0]?.entity || "";
const originalPage = kpiValues[0]?.page || 0; const originalPage = kpiValues[0]?.page || 0;
@ -102,6 +103,11 @@ function ExtractedResultsPage() {
// Um zu prüfen, ob der Wert nur aus Leerzeichen besteht // Um zu prüfen, ob der Wert nur aus Leerzeichen besteht
const isSelectedValueEmpty = selectedIndex === -1 ? customValue.trim() === "" : !selectedValue; const isSelectedValueEmpty = selectedIndex === -1 ? customValue.trim() === "" : !selectedValue;
const focusHighlight = focusHighlightOverride || {
page: groupedKpiValues.at(selectedIndex)?.pages[0] || -1,
text: groupedKpiValues.at(selectedIndex)?.entity || "",
};
useEffect(() => { useEffect(() => {
const valueChanged = selectedValue !== originalValue; const valueChanged = selectedValue !== originalValue;
const pageChanged = selectedPage !== originalPage; const pageChanged = selectedPage !== originalPage;
@ -159,12 +165,14 @@ function ExtractedResultsPage() {
const value = event.target.value; const value = event.target.value;
if (value === "custom") { if (value === "custom") {
setSelectedIndex(-1); setSelectedIndex(-1);
setFocusHighlightOverride(null);
} else { } else {
const index = Number.parseInt(value); const index = Number.parseInt(value);
setSelectedIndex(index); setSelectedIndex(index);
setCurrentPage(groupedKpiValues[index].pages[0]); setCurrentPage(groupedKpiValues[index].pages[0]);
setCustomValue(""); setCustomValue("");
setCustomPage(""); setCustomPage("");
setFocusHighlightOverride(null);
} }
}; };
@ -174,7 +182,8 @@ function ExtractedResultsPage() {
const value = event.target.value; const value = event.target.value;
setCustomValue(value); setCustomValue(value);
setSelectedIndex(-1); setSelectedIndex(-1);
}; setFocusHighlightOverride(null);
}
const handleCustomPageChange = ( const handleCustomPageChange = (
event: React.ChangeEvent<HTMLInputElement>, event: React.ChangeEvent<HTMLInputElement>,
@ -191,6 +200,15 @@ function ExtractedResultsPage() {
setSelectedIndex(index); setSelectedIndex(index);
setCustomValue(""); setCustomValue("");
setCustomPage(""); setCustomPage("");
setFocusHighlightOverride(null);
};
const handlePageClick = (page: number, entity: string) => {
setCurrentPage(page);
setFocusHighlightOverride({
page: page,
text: entity,
});
}; };
const handleBackClick = () => { const handleBackClick = () => {
@ -325,7 +343,7 @@ function ExtractedResultsPage() {
component="button" component="button"
onClick={(e: React.MouseEvent) => { onClick={(e: React.MouseEvent) => {
e.stopPropagation(); e.stopPropagation();
setCurrentPage(page); handlePageClick(page, item.entity);
}} }}
sx={{ cursor: "pointer", ml: i > 0 ? 1 : 0 }} sx={{ cursor: "pointer", ml: i > 0 ? 1 : 0 }}
> >
@ -351,6 +369,7 @@ function ExtractedResultsPage() {
}} }}
onClick={() => { onClick={() => {
setSelectedIndex(-1); setSelectedIndex(-1);
setFocusHighlightOverride(null);
}} }}
> >
<Radio <Radio
@ -479,10 +498,7 @@ function ExtractedResultsPage() {
highlight={groupedKpiValues highlight={groupedKpiValues
.map((k) => k.pages.map((page: number) => ({ page, text: k.entity }))) .map((k) => k.pages.map((page: number) => ({ page, text: k.entity })))
.reduce((acc, val) => acc.concat(val), [])} .reduce((acc, val) => acc.concat(val), [])}
focusHighlight={{ focusHighlight={focusHighlight}
page: groupedKpiValues.at(selectedIndex)?.pages[0] || -1,
text: groupedKpiValues.at(selectedIndex)?.entity || "",
}}
/> />
</Paper> </Paper>
<Box mt={2} display="flex" justifyContent="flex-end" gap={2}> <Box mt={2} display="flex" justifyContent="flex-end" gap={2}>