pse2_ff/project/frontend/src/components/KennzahlenTable.tsx

307 lines
7.9 KiB
TypeScript

import type { Kennzahl } from "@/types/kpi";
import EditIcon from "@mui/icons-material/Edit";
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
import SearchIcon from "@mui/icons-material/Search";
import {
Box,
Link,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TextField,
Tooltip,
} from "@mui/material";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { useNavigate } from "@tanstack/react-router";
import { useState } from "react";
import type { KeyboardEvent } from "react";
import { fetchPutKPI } from "../util/api";
interface KennzahlenTableProps {
onPageClick?: (page: number, text: string) => void;
pdfId: string;
settings: Kennzahl[];
data: {
[key: string]: {
label: string;
entity: string;
page: number;
status: string;
source: string;
}[];
};
}
export default function KennzahlenTable({
onPageClick,
data,
pdfId,
settings,
}: KennzahlenTableProps) {
const [editingIndex, setEditingIndex] = useState<string>("");
const [editValue, setEditValue] = useState("");
const navigate = useNavigate({ from: "/extractedResult/$pitchBook" });
const queryClient = useQueryClient();
const { mutate } = useMutation({
mutationFn: (id: string) => {
const key = id.toUpperCase();
const updatedData = { ...data };
updatedData[key] = data[key]?.map((item) => ({
...item,
entity: editValue,
})) || [{ label: key, entity: editValue }];
return fetchPutKPI(Number(pdfId), updatedData);
},
onMutate: async (id: string) => {
await queryClient.cancelQueries({
queryKey: ["pitchBookKPI", pdfId],
});
const snapshot = queryClient.getQueryData(["pitchBookKPI", pdfId]);
const key = id.toUpperCase();
queryClient.setQueryData(["pitchBookKPI", pdfId], () => {
const updatedData = { ...data };
updatedData[key] = data[key]?.map((item) => ({
...item,
entity: editValue,
})) || [{ label: key, entity: editValue }];
return updatedData;
});
return () => {
queryClient.setQueryData(["pitchBookKPI", pdfId], snapshot);
};
},
onError: (error, _variables, rollback) => {
console.log("error", error);
rollback?.();
},
onSettled: () => {
return queryClient.invalidateQueries({
queryKey: ["pitchBookKPI", pdfId],
});
},
});
// Bearbeitung starten
const startEditing = (value: string, index: string) => {
setEditingIndex(index);
setEditValue(value);
};
// Bearbeitung beenden und Wert speichern
const handleSave = async (index: string) => {
// await updateKennzahl(rows[index].label, editValue);
mutate(index);
setEditingIndex("");
};
// Tastatureingaben verarbeiten
const handleKeyPress = (e: KeyboardEvent<HTMLDivElement>, index: string) => {
if (e.key === "Enter") {
handleSave(index);
} else if (e.key === "Escape") {
setEditingIndex("null");
}
};
const handleNavigateToDetail = (settingName: string) => {
navigate({
to: "/extractedResult/$pitchBook/$kpi",
params: {
pitchBook: pdfId,
kpi: settingName,
},
});
};
return (
<TableContainer component={Paper}>
<Table>
<TableHead>
<TableRow>
<TableCell>
<strong>Kennzahl</strong>
</TableCell>
<TableCell>
<strong>Wert</strong>
</TableCell>
<TableCell align="center">
<strong>Seite</strong>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{settings
.filter((setting) => setting.active)
.sort((a, b) => a.position - b.position)
.map((setting) => ({
setting: setting,
extractedValues: data[setting.name.toUpperCase()] || [],
}))
.map((row) => {
let borderColor = "transparent";
const hasMultipleValues = row.extractedValues.length > 1;
const hasNoValue =
row.setting.mandatory &&
(row.extractedValues.length === 0 ||
row.extractedValues.at(0)?.entity === "");
if (hasNoValue) {
borderColor = "red";
} else if (hasMultipleValues) {
borderColor = "#f6ed48";
}
return (
<TableRow key={row.setting.name}>
<TableCell>{row.setting.name}</TableCell>
<TableCell
onClick={() => {
// Only allow inline editing for non-multiple value cells
if (!hasMultipleValues) {
startEditing(
row.extractedValues.at(0)?.entity || "",
row.setting.name,
);
} else {
// Navigate to detail page for multiple values
handleNavigateToDetail(row.setting.name);
}
}}
>
{hasMultipleValues ? (
<Tooltip
title={
<>
<b>Problem</b>
<br />
Mehrere Werte für die Kennzahl gefunden.
</>
}
placement="bottom"
arrow
>
<Box
sx={{
border: `2px solid ${borderColor}`,
borderRadius: 1,
padding: "4px 8px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
cursor: "pointer",
"&:hover": {
backgroundColor: "#f5f5f5",
},
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: 1,
width: "100%",
}}
>
<span>
{row.extractedValues.at(0)?.entity || "—"}
</span>
</Box>
<SearchIcon
fontSize="small"
sx={{ color: "#f6ed48" }}
/>
</Box>
</Tooltip>
) : (
<Box
sx={{
border: `2px solid ${borderColor}`,
borderRadius: 1,
padding: "4px 8px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: "100%",
cursor: "text",
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: 1,
width: "100%",
}}
>
{hasNoValue && (
<ErrorOutlineIcon fontSize="small" color="error" />
)}
{editingIndex === row.setting.name ? (
<TextField
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
onKeyDown={(e) =>
handleKeyPress(e, row.setting.name)
}
onBlur={() => handleSave(row.setting.name)}
autoFocus
size="small"
fullWidth
variant="standard"
sx={{ margin: "-8px 0" }}
/>
) : (
<span>
{row.extractedValues.at(0)?.entity || "—"}
</span>
)}
</Box>
<EditIcon
fontSize="small"
sx={{ color: "#555", cursor: "pointer" }}
onClick={(e) => {
e.stopPropagation();
startEditing(
row.extractedValues.at(0)?.entity || "",
row.setting.name,
);
}}
/>
</Box>
)}
</TableCell>
<TableCell align="center">
<Link
component="button"
onClick={() =>
onPageClick?.(
Number(row.extractedValues.at(0)?.page),
row.extractedValues.at(0)?.entity || "",
)
}
sx={{ cursor: "pointer" }}
>
{row.extractedValues.at(0)?.page}
</Link>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
);
}