feat(analytics): enrich empty-catalog signal with vehicle/category names #84

Merged
root merged 1 commits from dev into main 2026-06-03 14:19:27 +03:00
3 changed files with 53 additions and 3 deletions

View File

@@ -11,12 +11,24 @@ interface PartsPanelProps {
parts: Part[];
vehicleId?: string;
categoryId?: string;
// Human-readable context for analytics (e.g. the empty-catalog "0 parça"
// signal): which vehicle ("Audi A4 (2018)") and which category ("Fren
// sistemi") rendered. Best-effort — undefined on routes that don't thread it.
vehicleLabel?: string;
categoryName?: string;
isLoading?: boolean;
}
const SKELETON_ROW_KEYS = ["s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7"] as const;
export function PartsPanel({ parts, vehicleId, categoryId, isLoading }: PartsPanelProps) {
export function PartsPanel({
parts,
vehicleId,
categoryId,
vehicleLabel,
categoryName,
isLoading,
}: PartsPanelProps) {
const { highlightedGroup, selectedGroup, setHighlightedGroup, setSelectedGroup } =
useSchemaStore();
const navigate = useNavigate();
@@ -90,13 +102,26 @@ export function PartsPanel({ parts, vehicleId, categoryId, isLoading }: PartsPan
capture("parts_panel_viewed", {
vehicle_id: vehicleId,
category_id: categoryId,
// Names make the "0 parça" empty-catalog breakdown actionable (which
// vehicle/category to backfill) instead of opaque UUIDs.
vehicle_label: vehicleLabel,
category_name: categoryName,
parts_count: parts.length,
has_prices: hasPrices,
available_groups_count: new Set(
parts.filter((p) => p.hotspotIndex != null && !p.unavailable).map((p) => p.hotspotIndex),
).size,
});
}, [vehicleId, categoryId, parts.length, hasPrices, parts, isLoading]);
}, [
vehicleId,
categoryId,
vehicleLabel,
categoryName,
parts.length,
hasPrices,
parts,
isLoading,
]);
// Map group → IDs of available (non-unavailable) parts
const availableByGroup = useMemo(() => {
@@ -179,7 +204,21 @@ export function PartsPanel({ parts, vehicleId, categoryId, isLoading }: PartsPan
{parts.length === 0 ? (
<div className="flex flex-col items-center justify-center gap-4 py-12 text-center text-sm text-muted-foreground">
<p>Bu kategori için parça bulunamadı.</p>
<Button type="button" variant="outline" onClick={() => window.history.back()}>
<Button
type="button"
variant="outline"
onClick={() => {
// Demand/abandonment signal from an empty "0 parça" panel — lets
// us prioritise which catalogs to backfill, with readable context.
capture("empty_catalog_cta_clicked", {
vehicle_id: vehicleId,
category_id: categoryId,
vehicle_label: vehicleLabel,
category_name: categoryName,
});
window.history.back();
}}
>
Geri dön
</Button>
</div>

View File

@@ -16,6 +16,9 @@ interface SchemaViewerProps {
isLoading?: boolean;
vehicleId?: string;
categoryId?: string;
// Forwarded to PartsPanel for analytics context (empty-catalog signal).
vehicleLabel?: string;
categoryName?: string;
}
export function SchemaViewer({
@@ -25,6 +28,8 @@ export function SchemaViewer({
isLoading,
vehicleId,
categoryId,
vehicleLabel,
categoryName,
}: SchemaViewerProps) {
const { zoom, panX, panY, isFullscreen } = useSchemaStore();
const interaction = useSchemaInteraction();
@@ -101,6 +106,8 @@ export function SchemaViewer({
parts={parts}
vehicleId={vehicleId}
categoryId={categoryId}
vehicleLabel={vehicleLabel}
categoryName={categoryName}
isLoading={isLoading}
/>
</div>
@@ -180,6 +187,8 @@ export function SchemaViewer({
parts={parts}
vehicleId={vehicleId}
categoryId={categoryId}
vehicleLabel={vehicleLabel}
categoryName={categoryName}
isLoading={isLoading}
/>
</div>

View File

@@ -208,6 +208,8 @@ function VehicleCategoryPage() {
isLoading={isLoading}
vehicleId={id}
categoryId={categoryId}
vehicleLabel={vehicleLabel}
categoryName={data?.name}
/>
</Suspense>
))}