feat(p): link only OEM codes with a cross-reference match

Every OEM code was a link → unmatched codes (the majority — fasteners,
clips, body parts TecDoc doesn't carry) opened an empty "no
equivalents" page. Now the schema page batch-checks its codes against
the snapshot (POST /p/matched, one indexed query) and links ONLY the
matched ones; unmatched codes render as plain text. Copy stays for all.
No more dead-end empty pages from the parts panel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 00:21:25 +03:00
parent 572337ba62
commit 4ffc3577a5
3 changed files with 80 additions and 6 deletions

View File

@@ -37,6 +37,34 @@ export function PartsPanel({
const viewedKeyRef = useRef<string | null>(null);
const [copiedId, setCopiedId] = useState<string | null>(null);
const [resolvingCode, setResolvingCode] = useState<string | null>(null);
// OEM codes that resolve to a non-empty cross-reference page. Only these are
// rendered as links — unmatched codes stay plain text so a click never lands
// on an empty "no equivalents" page. One batch lookup per parts list.
const [matchedOemCodes, setMatchedOemCodes] = useState<Set<string>>(new Set());
const oemCodes = useMemo(
() => [...new Set(parts.map((p) => p.oemCode).filter((c) => c && c !== "N/A"))],
[parts],
);
useEffect(() => {
if (oemCodes.length === 0) {
setMatchedOemCodes(new Set());
return;
}
let cancelled = false;
api
.post<{ matched: string[] }>("/p/matched", { codes: oemCodes })
.then((res) => {
if (!cancelled) setMatchedOemCodes(new Set(res?.matched ?? []));
})
.catch(() => {
if (!cancelled) setMatchedOemCodes(new Set());
});
return () => {
cancelled = true;
};
}, [oemCodes]);
// PL24 "bk. tablo:NNN-NNN" cross-reference jump. Resolved at load → open the
// target illustration directly. Unresolved (target branch not seeded yet) →
@@ -365,11 +393,13 @@ export function PartsPanel({
)}
</button>
)}
{part.oemCode && part.oemCode !== "N/A" ? (
// Click → OEM detail page (P cross-reference) in a
// new tab so the catalog/schema context stays put. Plain
// anchor (not router Link) — a fresh load resolves the
// route and keeps this cell router-context-free.
{part.oemCode &&
part.oemCode !== "N/A" &&
matchedOemCodes.has(part.oemCode) ? (
// Link ONLY codes with a real cross-reference (new tab,
// keeps catalog/schema context). Plain anchor, not router
// Link — fresh load resolves the route, no router context
// needed here. Unmatched codes fall through to plain text.
<a
href={`/dashboard/oem/${encodeURIComponent(part.oemCode)}`}
target="_blank"