feat(catalog): kök-seviye canonical fallback + araç sayfası "Birleşik" toggle

Backend: groupCategoriesByCanonical'a requireParts opsiyonu; her iki serving
metodu artık parça-içeren yaprak yoksa (taze/drill-edilmemiş araç) kök
kategorileri kovalayarak boş dönmüyor.

Frontend: araç sayfasına "Katalog | Birleşik" toggle'ı; Birleşik seçilince
CanonicalView aracın kataloğunu 18 kanonik başlık altında gösterir, her kova
CategoryGrid ile normal drill'e bağlanır. canonical_view_opened event'i.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-04 18:28:07 +03:00
parent af2b35e6e2
commit cc1d22a7d4
5 changed files with 130 additions and 5 deletions

View File

@@ -2,6 +2,7 @@ import {
ReportCatalogIssueButton,
useReportCatalogDegradation,
} from "@/components/catalog/report-catalog-issue";
import { CanonicalView } from "@/components/categories/canonical-view";
import { CatalogSearch } from "@/components/categories/catalog-search";
import { CategoryBreadcrumb } from "@/components/categories/category-breadcrumb";
import { CategoryColumns } from "@/components/categories/category-columns";
@@ -56,6 +57,9 @@ function VehicleDetailPage() {
// When a cross-tree catalog search is active, the normal browse (and its view
// toggle) are hidden in favour of the search results.
const [searchActive, setSearchActive] = useState(false);
// "source" = the raw per-brand OEM tree (default); "canonical" = the same
// catalog folded into the unified 18-bucket taxonomy.
const [browseMode, setBrowseMode] = useState<"source" | "canonical">("source");
const changeViewMode = (mode: "grid" | "tree" | "columns") => {
setViewMode(mode);
@@ -308,9 +312,34 @@ function VehicleDetailPage() {
{/* Categories */}
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<CardHeader className="flex flex-row items-center justify-between gap-3">
<CardTitle className="text-base">{t("vehicle.categories")}</CardTitle>
{!searchActive && <CategoryViewToggle value={viewMode} onChange={changeViewMode} />}
{!searchActive && (
<div className="flex items-center gap-2">
<div className="inline-flex rounded-md border border-border p-0.5">
<button
type="button"
onClick={() => setBrowseMode("source")}
className={`rounded px-2.5 py-1 text-xs font-medium transition-colors ${browseMode === "source" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground"}`}
>
Katalog
</button>
<button
type="button"
onClick={() => {
setBrowseMode("canonical");
capture("canonical_view_opened", { vehicle_id: id });
}}
className={`rounded px-2.5 py-1 text-xs font-medium transition-colors ${browseMode === "canonical" ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground"}`}
>
Birleşik
</button>
</div>
{browseMode === "source" && (
<CategoryViewToggle value={viewMode} onChange={changeViewMode} />
)}
</div>
)}
</CardHeader>
<CardContent
className={
@@ -335,7 +364,9 @@ function VehicleDetailPage() {
</div>
)}
</div>
{!searchActive && browseMode === "canonical" ? <CanonicalView vehicleId={id} /> : null}
{!searchActive &&
browseMode === "source" &&
(categoriesLoading ? (
viewMode === "grid" ? (
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">