perf(p): instant OEM nav + trim 30x-bloated payload

OEM detail "slow load" was two things, not the query (DB 135ms / API
13-120ms):
1. The parts-panel link opened a NEW TAB → full SPA cold boot every
   click. Switch to in-app client navigation on plain click (real href
   kept, so ctrl/cmd/middle-click still opens a new tab).
2. The /p/oem response shipped each article's oeNumbers + compatible
   lists (up to 200 each × 60 articles) that the UI never renders — 96%
   of a 370 KB payload. Ship lean articles; aggregates already carry
   the cross-refs. 60-article code: 370 KB → ~12 KB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-10 01:06:29 +03:00
parent f29730fae0
commit 5a13f13e8e
4 changed files with 34 additions and 12 deletions

View File

@@ -240,7 +240,18 @@ export class PSourceDbService implements OnModuleInit, OnModuleDestroy {
query,
queryNorm,
matched: true,
articles,
// Ship lean articles — the per-article oeNumbers/compatible lists were
// only needed above to build the aggregates; sending them too bloated
// heavy responses ~30x (370 KB → ~12 KB for a 60-article code).
articles: articles.map((a) => ({
id: a.id,
brand: a.brand,
articleNumber: a.articleNumber,
name: a.name,
spareInfo: a.spareInfo,
images: a.images,
eans: a.eans,
})),
aftermarketParts,
oeCrossReferences,
truncated,

View File

@@ -17,7 +17,9 @@ export interface PImage {
thumb: string | null;
}
/** One P article whose OE number list contains the queried OEM code. */
/** One P article whose OE number list contains the queried OEM code. Internal
* shape — `oeNumbers`/`compatible` feed the aggregates and are NOT shipped to
* the client (they're large and unused there; see PArticleSummary). */
export interface PArticle {
id: string;
brand: string;
@@ -30,6 +32,11 @@ export interface PArticle {
compatible: PCompatible[];
}
/** What the OEM detail page actually renders per matched article — without the
* heavy per-article OE/compat lists (those are deduped into the top-level
* aggregates). Trimming them cut a 60-article payload from ~370 KB to ~12 KB. */
export type PArticleSummary = Omit<PArticle, "oeNumbers" | "compatible">;
/** Response of the OEM detail lookup. `matched: false` covers every miss —
* feature disabled, code too short, or no P article carries that OE
* number — so the UI has a single empty-state path. */
@@ -37,8 +44,9 @@ export interface POemResult {
query: string;
queryNorm: string;
matched: boolean;
/** Distinct articles whose OE list contains the queried code. */
articles: PArticle[];
/** Distinct articles whose OE list contains the queried code (lean — per-article
* OE/compat lists are aggregated below, not duplicated here). */
articles: PArticleSummary[];
/** Deduped buyable aftermarket part numbers across all matched articles
* (the matched articles themselves + their compatibility entries). */
aftermarketParts: Array<{ brand: string; articleNumber: string; thumb: string | null }>;

View File

@@ -396,14 +396,13 @@ export function PartsPanel({
{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.
// Link ONLY codes with a real cross-reference. Plain
// left-click → in-app client navigation (no full SPA
// reload — the new-tab boot was the slow part). Real href
// kept so ctrl/cmd/middle-click still opens a new tab.
// Unmatched codes fall through to plain text.
<a
href={`/dashboard/oem/${encodeURIComponent(part.oemCode)}`}
target="_blank"
rel="noopener noreferrer"
title="Uyumlu parça kodlarını gör"
className="underline decoration-dotted underline-offset-2 transition-colors hover:text-foreground hover:decoration-solid"
onClick={(e) => {
@@ -414,6 +413,12 @@ export function PartsPanel({
vehicle_id: vehicleId,
category_id: categoryId,
});
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
e.preventDefault();
navigate({
to: "/dashboard/oem/$code",
params: { code: part.oemCode },
});
}}
>
{part.oemCode}

View File

@@ -29,8 +29,6 @@ interface PArticle {
spareInfo: string | null;
images: Array<{ url: string; thumb: string | null }>;
eans: string[];
oeNumbers: OeNumber[];
compatible: Array<{ brand: string; article: string }>;
}
interface POemResult {
query: string;