122 lines
4.6 KiB
TypeScript
122 lines
4.6 KiB
TypeScript
import React from "react";
|
|
import {
|
|
Box,
|
|
Paper,
|
|
Typography,
|
|
Button,
|
|
Alert,
|
|
} from "@mui/material";
|
|
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
|
|
import { useFetchRequestAmbiguities, useResolveAmbiguity } from "../../features/fetch-requests";
|
|
|
|
interface AmbiguityResolverProps {
|
|
fetchRequestId: string;
|
|
}
|
|
|
|
export function AmbiguityResolver({ fetchRequestId }: AmbiguityResolverProps) {
|
|
const { data: ambiguities, refetch } = useFetchRequestAmbiguities(fetchRequestId);
|
|
const resolveMutation = useResolveAmbiguity();
|
|
|
|
const handleResolve = async (ambiguity: any, candidate: { amount: number; balance: number }) => {
|
|
await resolveMutation.mutateAsync({
|
|
ambiguityId: ambiguity.id,
|
|
payload: { chosen: { amount: candidate.amount, balance: candidate.balance } },
|
|
});
|
|
refetch();
|
|
};
|
|
|
|
if (!ambiguities || ambiguities.length === 0) return null;
|
|
|
|
const allResolved = ambiguities.every((a: any) => a.status === "resolved");
|
|
|
|
return (
|
|
<Paper sx={{ p: 3, borderRadius: 4, mb: 3 }} variant="outlined">
|
|
<Typography variant="subtitle1" fontWeight={600} gutterBottom>
|
|
Ambiguity Resolution
|
|
</Typography>
|
|
|
|
{allResolved ? (
|
|
<Alert severity="success" sx={{ mb: 2, borderRadius: 2 }}>
|
|
All ambiguities resolved — pipeline will resume on next poll cycle
|
|
</Alert>
|
|
) : (
|
|
<Alert severity="warning" sx={{ mb: 2, borderRadius: 2 }}>
|
|
Pipeline paused — resolve ambiguities to continue
|
|
</Alert>
|
|
)}
|
|
|
|
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
|
|
{ambiguities.map((ambiguity: any) => {
|
|
const isResolved = ambiguity.status === "resolved";
|
|
return (
|
|
<Paper
|
|
key={ambiguity.id}
|
|
sx={{
|
|
p: 2,
|
|
borderRadius: 3,
|
|
border: 1,
|
|
borderColor: isResolved ? "success.main" : "divider",
|
|
opacity: isResolved ? 0.8 : 1,
|
|
}}
|
|
variant="outlined"
|
|
>
|
|
<Box sx={{ fontFamily: "monospace", fontSize: "0.85rem", mb: 1.5, p: 1, bgcolor: "grey.900", borderRadius: 1, color: "grey.100" }}>
|
|
{ambiguity.line}
|
|
</Box>
|
|
|
|
<Box sx={{ display: "flex", gap: 3, mb: 1.5, flexWrap: "wrap" }}>
|
|
<Box>
|
|
<Typography variant="caption" color="text.secondary">OCR Amount</Typography>
|
|
<Typography variant="body2" sx={{ textDecoration: "line-through", color: "text.secondary" }}>
|
|
₹{ambiguity.ocr_amount}
|
|
</Typography>
|
|
</Box>
|
|
<Box>
|
|
<Typography variant="caption" color="text.secondary">OCR Balance</Typography>
|
|
<Typography variant="body2" sx={{ textDecoration: "line-through", color: "text.secondary" }}>
|
|
₹{ambiguity.ocr_balance}
|
|
</Typography>
|
|
</Box>
|
|
<Box>
|
|
<Typography variant="caption" color="text.secondary">Previous Balance</Typography>
|
|
<Typography variant="body2">₹{ambiguity.prev_balance}</Typography>
|
|
</Box>
|
|
</Box>
|
|
|
|
{isResolved ? (
|
|
<Alert severity="success" sx={{ py: 0.5, borderRadius: 2 }} icon={<CheckCircleIcon />}>
|
|
Resolved: ₹{ambiguity.chosen?.amount} / ₹{ambiguity.chosen?.balance}
|
|
</Alert>
|
|
) : (
|
|
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
|
|
{ambiguity.candidates.map((candidate: any, ci: number) => {
|
|
const isCredit = candidate.amount > 0;
|
|
const isDebit = candidate.amount < 0;
|
|
const cColor = isCredit ? "success.main" : isDebit ? "error.main" : undefined;
|
|
return (
|
|
<Button
|
|
key={ci}
|
|
variant="outlined"
|
|
size="small"
|
|
onClick={() => handleResolve(ambiguity, candidate)}
|
|
disabled={resolveMutation.isPending}
|
|
sx={{
|
|
borderColor: cColor,
|
|
color: cColor,
|
|
"&:hover": cColor ? { borderColor: cColor } : undefined,
|
|
}}
|
|
>
|
|
₹{candidate.amount} / ₹{candidate.balance}
|
|
</Button>
|
|
);
|
|
})}
|
|
</Box>
|
|
)}
|
|
</Paper>
|
|
);
|
|
})}
|
|
</Box>
|
|
</Paper>
|
|
);
|
|
}
|