feat(web): make vehicle info card a collapsible accordion

Collapse the Araç Bilgileri card by default. The always-visible header now
shows an at-a-glance summary (Model, Model yılı, Motor kodu); expanding reveals
the full attribute grid, which also gains the decoded drive type (Çekiş /
"Önden çekiş") from rawData. The loading skeleton now matches the collapsed
header so there's no layout jump when data lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-25 03:27:55 +03:00
parent 531ab35a89
commit 2075631c65

View File

@@ -5,7 +5,16 @@ import { CategoryViewToggle } from "@/components/categories/category-view-toggle
import { CarBrandLogo } from "@/components/ui/car-brand-logo";
import { ApiError, api } from "@/lib/api-client";
import { getUserSettings, setUserSetting } from "@/lib/user-settings";
import { Card, CardContent, CardHeader, CardTitle } from "@sase/ui";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
Card,
CardContent,
CardHeader,
CardTitle,
} from "@sase/ui";
import { Skeleton } from "@sase/ui";
import { Button } from "@sase/ui";
import { useQuery } from "@tanstack/react-query";
@@ -13,7 +22,7 @@ import { createFileRoute, useCanGoBack, useNavigate, useRouter } from "@tanstack
import { ArrowLeft } from "lucide-react";
import { useState } from "react";
import { KEYS_6, KEYS_8 } from "@/lib/keys";
import { KEYS_8 } from "@/lib/keys";
export const Route = createFileRoute("/dashboard/vehicles_/$id/")({
component: VehicleDetailPage,
});
@@ -74,17 +83,10 @@ function VehicleDetailPage() {
<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>
{/* Vehicle info card skeleton: collapsed accordion header */}
<div className="rounded-xl border border-border px-6 py-4">
<Skeleton className="mb-2 h-5 w-32" />
<Skeleton className="h-3 w-64" />
</div>
{/* Categories skeleton: 8 rows */}
<div className="rounded-lg border border-border p-6">
@@ -179,14 +181,22 @@ function VehicleDetailPage() {
</div>
</div>
{/* Vehicle Info */}
{/* Vehicle Info — collapsed by default; the trigger shows an at-a-glance
summary, expanding reveals the full attribute + equipment detail. */}
<Card>
<CardHeader>
<CardTitle className="text-base">Araç Bilgileri</CardTitle>
</CardHeader>
<CardContent>
<VehicleAttributes vehicle={vehicle} />
</CardContent>
<Accordion type="single" collapsible>
<AccordionItem value="info" className="border-b-0">
<AccordionTrigger className="px-6 hover:no-underline">
<div className="flex flex-1 flex-col items-start gap-1.5 pr-3 text-left">
<span className="text-base font-semibold">Araç Bilgileri</span>
<VehicleSummary vehicle={vehicle} />
</div>
</AccordionTrigger>
<AccordionContent className="px-6 pb-6">
<VehicleAttributes vehicle={vehicle} />
</AccordionContent>
</AccordionItem>
</Accordion>
</Card>
{/* Categories */}
@@ -263,6 +273,7 @@ function VehicleAttributes({ vehicle }: { vehicle: any }) {
// decoded codes follow — instead of the codes hiding the readable data.
const readableExtras = [
{ label: "Motor", value: vehicle?.engine },
{ label: "Çekiş", value: vehicle?.rawData?.driveType },
{ label: "Kasa", value: vehicle?.bodyType },
].filter((a): a is { label: string; value: string } => Boolean(a.value));
@@ -304,3 +315,24 @@ function VehicleAttributes({ vehicle }: { vehicle: any }) {
</dl>
);
}
/** Compact at-a-glance fields shown on the collapsed Araç Bilgileri header. */
function VehicleSummary({ vehicle }: { vehicle: any }) {
const fields = [
{ label: "Model", value: vehicle?.model },
{ label: "Model yılı", value: vehicle?.year },
{ label: "Motor kodu", value: vehicle?.rawData?.engineCode },
].filter((f) => f.value != null && f.value !== "");
if (fields.length === 0) return null;
return (
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs font-normal">
{fields.map((f) => (
<span key={f.label} className="text-muted-foreground">
{f.label}: <span className="font-medium tabular-nums text-foreground">{f.value}</span>
</span>
))}
</div>
);
}