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 PlayArrowIcon from "@mui/icons-material/PlayArrow";
import RemoveCircleOutlineIcon from "@mui/icons-material/RemoveCircleOutline"; import RemoveCircleOutlineIcon from "@mui/icons-material/RemoveCircleOutline";
import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord"; 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 { useQuery } from "@tanstack/react-query";
import { RETRY_MAX, formatApiError } from "../features/fetch-requests"; import { RETRY_MAX, formatApiError } from "../features/fetch-requests";
import type { FetchRequestStatus, SSEEvent, ProgressMessage } 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 { id } = useParams<{ id: string }>();
const navigate = useNavigate(); const navigate = useNavigate();
const { get, patch, resource } = useResource("fetch-requests"); const { get, patch, resource } = useResource("fetch-requests");
const { resources: allResources } = useAppContext();
const [stepStats, setStepStats] = useState<Record<string, number>>({}); const [stepStats, setStepStats] = useState<Record<string, number>>({});
const [liveParsedCount, setLiveParsedCount] = useState<number | undefined>(undefined); const [liveParsedCount, setLiveParsedCount] = useState<number | undefined>(undefined);
const [retrying, setRetrying] = useState(false); 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) { if (isLoading) {
return ( return (
<Box sx={{ display: "flex", justifyContent: "center", p: 8 }}> <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 ( return (
<Container sx={{ mt: 4, mb: 4 }}> <Container sx={{ mt: 4, mb: 4 }}>
<Button startIcon={<ArrowBackIcon />} onClick={() => navigate("/fetch-requests")} sx={{ mb: 2 }}> <Button startIcon={<ArrowBackIcon />} onClick={() => navigate("/fetch-requests")} sx={{ mb: 2 }}>
@@ -199,7 +214,7 @@ export default function FetchRequestDetail() {
label={status.replace(/_/g, " ")} label={status.replace(/_/g, " ")}
color={statusColors[status]} color={statusColors[status]}
/> />
<Typography variant="h6" fontWeight={600}>{req.account_name}</Typography> <Typography variant="h6" fontWeight={600}>{displayTitle}</Typography>
<Chip <Chip
label={"path" in (req.source ?? {}) ? "File" : "Email"} label={"path" in (req.source ?? {}) ? "File" : "Email"}
size="small" size="small"
@@ -209,14 +224,22 @@ export default function FetchRequestDetail() {
</Box> </Box>
<Box sx={{ display: "flex", gap: 4, flexWrap: "wrap", mb: 2 }}> <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 <DetailFieldRenderer
key={field.name} key={field.name}
field={field} field={field}
value={req[field.name]} value={value}
displayFormat={resource?.displayFormat} displayFormat={fmt}
/> />
))} );
})}
</Box> </Box>
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}> <Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>