Add AI review workflow for Paperless documents
This commit is contained in:
154
frontend/src/routes/AiReviewList.tsx
Normal file
154
frontend/src/routes/AiReviewList.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import AutoAwesomeIcon from "@mui/icons-material/AutoAwesome";
|
||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||
import {
|
||||
Alert,
|
||||
Box,
|
||||
Button,
|
||||
Chip,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { fetchAiReviews } from "../api/aiReviews";
|
||||
import { fetchServerConfig } from "../api/config";
|
||||
import PageHeader from "../components/PageHeader";
|
||||
import { formatDate } from "../utils/date";
|
||||
|
||||
function statusColor(status: string): "default" | "primary" | "success" | "warning" | "error" {
|
||||
if (status === "approved") return "success";
|
||||
if (status === "needs_review") return "primary";
|
||||
if (status === "failed") return "error";
|
||||
if (status === "analyzing" || status === "pending") return "warning";
|
||||
return "default";
|
||||
}
|
||||
|
||||
export default function AiReviewList() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
data: reviews,
|
||||
isLoading,
|
||||
isError,
|
||||
refetch,
|
||||
isFetching
|
||||
} = useQuery({
|
||||
queryKey: ["ai-reviews"],
|
||||
queryFn: () => fetchAiReviews()
|
||||
});
|
||||
const { data: serverConfig } = useQuery({
|
||||
queryKey: ["server-config"],
|
||||
queryFn: fetchServerConfig
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title={t("aiReviews.title")}
|
||||
subtitle={t("aiReviews.subtitle")}
|
||||
action={
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<RefreshIcon />}
|
||||
onClick={() => refetch()}
|
||||
disabled={isFetching}
|
||||
>
|
||||
{t("aiReviews.refresh")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
{!serverConfig?.aiConfigured && (
|
||||
<Alert severity="warning" sx={{ mb: 2 }}>
|
||||
{t("aiReviews.aiNotConfigured")}
|
||||
</Alert>
|
||||
)}
|
||||
{!serverConfig?.paperlessWebhookConfigured && (
|
||||
<Alert severity="info" sx={{ mb: 2 }}>
|
||||
{t("aiReviews.webhookNotConfigured")}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Paper variant="outlined" sx={{ borderRadius: 3, p: 2.5 }}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>{t("aiReviews.columns.document")}</TableCell>
|
||||
<TableCell>{t("aiReviews.columns.status")}</TableCell>
|
||||
<TableCell>{t("aiReviews.columns.confidence")}</TableCell>
|
||||
<TableCell>{t("aiReviews.columns.updated")}</TableCell>
|
||||
<TableCell align="right">{t("aiReviews.columns.action")}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5}>{t("messages.loading")}</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
{isError && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5}>
|
||||
<Typography color="error">{t("aiReviews.loadError")}</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
{!isLoading && !isError && (reviews ?? []).length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5}>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<AutoAwesomeIcon color="disabled" />
|
||||
<Typography color="text.secondary">{t("aiReviews.empty")}</Typography>
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
{(reviews ?? []).map((review) => (
|
||||
<TableRow
|
||||
hover
|
||||
key={review.id}
|
||||
sx={{ cursor: "pointer" }}
|
||||
onClick={() => navigate(`/ai-reviews/${review.id}`)}
|
||||
>
|
||||
<TableCell>
|
||||
<Typography fontWeight={600}>
|
||||
{review.documentTitle ?? `${t("aiReviews.document")} #${review.paperlessDocumentId}`}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Paperless #{review.paperlessDocumentId}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Chip
|
||||
size="small"
|
||||
color={statusColor(review.status)}
|
||||
label={t(`aiReviews.status.${review.status}`)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{review.confidence !== null ? `${Math.round(review.confidence * 100)}%` : "-"}
|
||||
</TableCell>
|
||||
<TableCell>{formatDate(review.updatedAt, "dd.MM.yyyy HH:mm")}</TableCell>
|
||||
<TableCell align="right">
|
||||
<Button size="small" onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
navigate(`/ai-reviews/${review.id}`);
|
||||
}}>
|
||||
{t("aiReviews.review")}
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Paper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user