fix(fetch-requests): resolve FK displayFormat in custom detail page

- Import useAppContext and applyDisplayFormat for target resource resolution
- Fix detail field rendering to use FK target resource displayFormat
- Fix title to resolve FK objects via displayFormat instead of non-existent account_name
- Move displayTitle useMemo before early returns to avoid hook ordering violation
This commit is contained in:
2026-07-31 00:17:40 +05:30
parent c705325855
commit 3c8a17f336

View File

@@ -19,7 +19,7 @@ import WarningAmberIcon from "@mui/icons-material/WarningAmber";
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
import RemoveCircleOutlineIcon from "@mui/icons-material/RemoveCircleOutline";
import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord";
import { useResource, useItemSse, DetailFieldRenderer } from "../../react-openapi";
import { useResource, useItemSse, useAppContext, DetailFieldRenderer, applyDisplayFormat } from "../../react-openapi";
import { useQuery } from "@tanstack/react-query";
import { RETRY_MAX, formatApiError } from "../features/fetch-requests";
import type { FetchRequestStatus, SSEEvent, ProgressMessage } from "../features/fetch-requests";
@@ -71,6 +71,7 @@ export default function FetchRequestDetail() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { get, patch, resource } = useResource("fetch-requests");
const { resources: allResources } = useAppContext();
const [stepStats, setStepStats] = useState<Record<string, number>>({});
const [liveParsedCount, setLiveParsedCount] = useState<number | undefined>(undefined);
const [retrying, setRetrying] = useState(false);
@@ -158,6 +159,29 @@ export default function FetchRequestDetail() {
}
};
const req = fetchRequest as any;
const retryCount = req?.retry_count ?? 0;
const isRetryExhausted = retryCount >= RETRY_MAX;
const status = req?.status as FetchRequestStatus | undefined;
const detailFields = (resource?.orderedFields ?? []).filter(
(f) => f.name !== "source",
);
const displayTitle = useMemo(() => {
if (!resource || !req) return "";
const resolved = Object.fromEntries(
resource.orderedFields.map((field) => {
const value = req[field.name];
if (field.fk && typeof value === "object" && value != null) {
const target = allResources.find((r) => r.name === field.fk!.resource);
if (target) return [field.name, applyDisplayFormat(value, target.displayFormat)];
}
return [field.name, value];
}),
);
return applyDisplayFormat(resolved, resource.displayFormat);
}, [resource, req, allResources]);
if (isLoading) {
return (
<Box sx={{ display: "flex", justifyContent: "center", p: 8 }}>
@@ -177,15 +201,6 @@ export default function FetchRequestDetail() {
);
}
const req = fetchRequest as any;
const retryCount = req.retry_count ?? 0;
const isRetryExhausted = retryCount >= RETRY_MAX;
const status = req.status as FetchRequestStatus;
const detailFields = (resource?.orderedFields ?? []).filter(
(f) => f.name !== "source",
);
return (
<Container sx={{ mt: 4, mb: 4 }}>
<Button startIcon={<ArrowBackIcon />} onClick={() => navigate("/fetch-requests")} sx={{ mb: 2 }}>
@@ -199,7 +214,7 @@ export default function FetchRequestDetail() {
label={status.replace(/_/g, " ")}
color={statusColors[status]}
/>
<Typography variant="h6" fontWeight={600}>{req.account_name}</Typography>
<Typography variant="h6" fontWeight={600}>{displayTitle}</Typography>
<Chip
label={"path" in (req.source ?? {}) ? "File" : "Email"}
size="small"
@@ -209,14 +224,22 @@ export default function FetchRequestDetail() {
</Box>
<Box sx={{ display: "flex", gap: 4, flexWrap: "wrap", mb: 2 }}>
{detailFields.map((field) => (
{detailFields.map((field) => {
const value = req[field.name];
let fmt = resource?.displayFormat;
if (field.fk && typeof value === "object" && value != null) {
const target = allResources.find((r) => r.name === field.fk!.resource);
if (target) fmt = target.displayFormat;
}
return (
<DetailFieldRenderer
key={field.name}
field={field}
value={req[field.name]}
displayFormat={resource?.displayFormat}
value={value}
displayFormat={fmt}
/>
))}
);
})}
</Box>
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>