fix(vehicle-page): proper dl semantics, layout-matched skeleton, empty state
- VehicleAttributes wrapped in <dl> (was emitting <dt>/<dd> with no list
parent). Both the rich vinfoBasic branch and the DB-fallback branch now
share one layout instead of diverging into span-based and dt/dd-based
trees — easier to style and screen-reader friendly.
- New explicit empty state ("Bu araç için ayrıntı bilgisi bulunamadı") when
both branches resolve to nothing. Previously rendered an empty grid.
- Loading skeleton now mirrors the actual page structure (header chip + logo
+ 2-line title, info card with 6 attribute slots, categories card with 8
rows). The previous three plain rectangles caused a layout jump on resolve.
- Attribute rows use small-caps muted labels and tabular-nums values for
scannable data (engine codes, mileages, years).
Phase 7/8 of the UX audit follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ import { createFileRoute } from "@tanstack/react-router";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { KEYS_8 } from "@/lib/keys";
|
||||
import { KEYS_6, KEYS_8 } from "@/lib/keys";
|
||||
export const Route = createFileRoute("/dashboard/vehicles_/$id/")({
|
||||
component: VehicleDetailPage,
|
||||
});
|
||||
@@ -44,9 +44,36 @@ function VehicleDetailPage() {
|
||||
if (vehicleLoading) {
|
||||
return (
|
||||
<div className="mx-auto max-w-4xl space-y-6">
|
||||
<Skeleton className="h-8 w-64" />
|
||||
<Skeleton className="h-32 w-full" />
|
||||
<Skeleton className="h-64 w-full" />
|
||||
{/* Header skeleton: back button + logo + 2-line title */}
|
||||
<div className="flex items-center gap-3">
|
||||
<Skeleton className="h-9 w-9 rounded-md" />
|
||||
<Skeleton className="h-8 w-8 rounded-md" />
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Skeleton className="h-6 w-56" />
|
||||
<Skeleton className="h-4 w-40" />
|
||||
</div>
|
||||
</div>
|
||||
{/* Vehicle info card skeleton: 4-cell attribute grid */}
|
||||
<div className="rounded-lg border border-border p-6">
|
||||
<Skeleton className="mb-4 h-5 w-32" />
|
||||
<div className="grid grid-cols-2 gap-x-6 gap-y-4 sm:grid-cols-3">
|
||||
{KEYS_6.map((__k) => (
|
||||
<div key={__k} className="flex flex-col gap-1.5">
|
||||
<Skeleton className="h-3 w-20" />
|
||||
<Skeleton className="h-4 w-28" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{/* Categories skeleton: 8 rows */}
|
||||
<div className="rounded-lg border border-border p-6">
|
||||
<Skeleton className="mb-4 h-5 w-44" />
|
||||
<div className="space-y-2">
|
||||
{KEYS_8.map((__k) => (
|
||||
<Skeleton key={__k} className="h-11 w-full" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -128,39 +155,36 @@ function getVehicleAttributes(vehicle: any): Array<{ label: string; value: strin
|
||||
}
|
||||
|
||||
function VehicleAttributes({ vehicle }: { vehicle: any }) {
|
||||
const attrs = getVehicleAttributes(vehicle);
|
||||
const richAttrs = getVehicleAttributes(vehicle);
|
||||
const fallbackAttrs = [
|
||||
{ label: "Marka", value: vehicle?.brandName },
|
||||
{ label: "Model", value: vehicle?.model },
|
||||
{ label: "Yıl", value: vehicle?.year },
|
||||
{ label: "Motor", value: vehicle?.engine },
|
||||
{ label: "Vites", value: vehicle?.transmission },
|
||||
{ label: "Kasa", value: vehicle?.bodyType },
|
||||
].filter((a): a is { label: string; value: string } => Boolean(a.value));
|
||||
|
||||
// Prefer the rich VIN-decoded attributes when available; otherwise show DB
|
||||
// fallback fields. Single layout for both — keeps semantics consistent.
|
||||
const attrs = richAttrs.length > 0 ? richAttrs : fallbackAttrs;
|
||||
|
||||
if (attrs.length === 0) {
|
||||
// Fallback: show basic DB fields
|
||||
const fallback = [
|
||||
{ label: "Marka", value: vehicle?.brandName },
|
||||
{ label: "Model", value: vehicle?.model },
|
||||
{ label: "Yıl", value: vehicle?.year },
|
||||
{ label: "Motor", value: vehicle?.engine },
|
||||
{ label: "Vites", value: vehicle?.transmission },
|
||||
{ label: "Kasa", value: vehicle?.bodyType },
|
||||
].filter((a) => a.value);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-4 text-sm md:grid-cols-4">
|
||||
{fallback.map((attr) => (
|
||||
<div key={attr.label}>
|
||||
<span className="text-muted-foreground">{attr.label}:</span>{" "}
|
||||
<span className="font-medium">{attr.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Bu araç için ayrıntı bilgisi bulunamadı.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-3 text-sm sm:grid-cols-2">
|
||||
{attrs.map((attr: { label: string; value: string }) => (
|
||||
<div key={attr.label}>
|
||||
<dt className="text-muted-foreground">{attr.label}</dt>
|
||||
<dd className="font-medium">{attr.value}</dd>
|
||||
<dl className="grid grid-cols-1 gap-x-6 gap-y-3 text-sm sm:grid-cols-2 md:grid-cols-3">
|
||||
{attrs.map((attr) => (
|
||||
<div key={attr.label} className="flex flex-col">
|
||||
<dt className="text-xs uppercase tracking-wide text-muted-foreground">{attr.label}</dt>
|
||||
<dd className="font-medium tabular-nums">{attr.value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</dl>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user