i18n(web): translate the vehicle catalog detail surface
Move every hard-coded Turkish string on /dashboard/vehicles/:id and the drill-down /categories/:categoryId page into a vehicle.* message block (tr + en) — page header, error/404 panels with retry, "Araç Bilgileri" accordion, "Yedek Parça Kategorileri" + the new empty state, equipment title and show-more/less toggle, summary labels — plus the leaf-category catalog-error panel and the drill-down's loading/retry copy. Also translate the CatalogSearch component (placeholder, arias, searching/treeIncomplete/noMatch hints, section headings, parts-count badge) under a new catalogSearch.* block, since it lives inside the same surface and the rest of the page already speaks two languages. PCAT_LABELS / EMEX_LABELS / VINAPI_FIELDS / LABEL_OVERRIDES are source-specific data tables, not UI chrome — left as-is. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { api } from "@/lib/api-client";
|
||||
import { getCategoryIcon } from "@/lib/category-icons";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import { Badge, Input } from "@sase/ui";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
@@ -71,6 +72,7 @@ export function CatalogSearch({
|
||||
vehicleId: string;
|
||||
onActiveChange?: (active: boolean) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [input, setInput] = useState("");
|
||||
const debounced = useDebounced(input.trim(), 300);
|
||||
@@ -107,8 +109,8 @@ export function CatalogSearch({
|
||||
type="search"
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
placeholder="Tüm katalogda ara — ör. fren balatası veya OEM kodu"
|
||||
aria-label="Tüm katalogda ara"
|
||||
placeholder={t("catalogSearch.placeholder")}
|
||||
aria-label={t("catalogSearch.ariaLabel")}
|
||||
className="pl-9 pr-9"
|
||||
/>
|
||||
{active && isFetching ? (
|
||||
@@ -117,7 +119,7 @@ export function CatalogSearch({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setInput("")}
|
||||
aria-label="Aramayı temizle"
|
||||
aria-label={t("catalogSearch.clearAria")}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
@@ -129,33 +131,33 @@ export function CatalogSearch({
|
||||
<div className="space-y-6">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center gap-2 py-8 text-sm text-muted-foreground">
|
||||
<Loader2 className="h-4 w-4 animate-spin" /> Aranıyor…
|
||||
<Loader2 className="h-4 w-4 animate-spin" /> {t("catalogSearch.searching")}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{data?.treeIncomplete && (
|
||||
<div className="flex items-start gap-2 rounded-lg border border-amber-500/30 bg-amber-500/5 p-3 text-xs text-muted-foreground">
|
||||
<Info className="mt-0.5 h-4 w-4 flex-shrink-0 text-amber-500" />
|
||||
<span>
|
||||
Bu aracın kataloğu henüz tam indirilmemiş. Arama yalnızca şu ana kadar açılmış
|
||||
kategorileri kapsar — kategorileri gezdikçe kapsam genişler.
|
||||
</span>
|
||||
<span>{t("catalogSearch.treeIncomplete")}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nothing ? (
|
||||
<p className="py-8 text-center text-sm text-muted-foreground">
|
||||
"{debounced}" ile eşleşen kategori veya parça bulunamadı.
|
||||
{t("catalogSearch.noMatch", { query: debounced })}
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
{/* Section 1 — leaf categories */}
|
||||
<section>
|
||||
<h3 className="mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Kategoriler {categories.length > 0 && `(${categories.length})`}
|
||||
{t("catalogSearch.categoriesHeading")}{" "}
|
||||
{categories.length > 0 && `(${categories.length})`}
|
||||
</h3>
|
||||
{categories.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">Eşleşen kategori yok.</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("catalogSearch.noCategoryMatch")}
|
||||
</p>
|
||||
) : (
|
||||
<ul className="space-y-1.5">
|
||||
{categories.map((c) => {
|
||||
@@ -179,7 +181,9 @@ export function CatalogSearch({
|
||||
variant="secondary"
|
||||
className="flex-shrink-0 text-[11px]"
|
||||
>
|
||||
{c.matchingPartCount} parça
|
||||
{t("catalogSearch.partsCount", {
|
||||
count: c.matchingPartCount,
|
||||
})}
|
||||
</Badge>
|
||||
)}
|
||||
</span>
|
||||
@@ -197,11 +201,11 @@ export function CatalogSearch({
|
||||
{/* Section 2 — parts (OEM) */}
|
||||
<section>
|
||||
<h3 className="mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Parçalar (OEM) {parts.length > 0 && `(${parts.length})`}
|
||||
{t("catalogSearch.partsHeading")} {parts.length > 0 && `(${parts.length})`}
|
||||
</h3>
|
||||
{parts.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Eşleşen parça yok. (Yalnızca açılmış kategorilerin parçaları aranır.)
|
||||
{t("catalogSearch.noPartMatch")}
|
||||
</p>
|
||||
) : (
|
||||
<ul className="space-y-1.5">
|
||||
|
||||
@@ -582,5 +582,44 @@
|
||||
"pasteInvalidChars": "VIN cannot contain I, O, Q",
|
||||
"candidateSelectFailed": "Vehicle selection failed",
|
||||
"autoCorrected": "Auto-corrected: {list}"
|
||||
},
|
||||
"vehicle": {
|
||||
"title": "Vehicle",
|
||||
"back": "Back",
|
||||
"notFoundTitle": "Vehicle not found",
|
||||
"errorTitle": "Couldn't load vehicle",
|
||||
"notFoundMsg": "This vehicle record was not found",
|
||||
"errorMsg": "Couldn't load vehicle details",
|
||||
"notFoundHint": "The link may be invalid or the record removed.",
|
||||
"errorHint": "An error occurred loading the data.",
|
||||
"retry": "Try again",
|
||||
"info": "Vehicle details",
|
||||
"categories": "Spare parts categories",
|
||||
"noCategoriesTitle": "No categories for this vehicle",
|
||||
"noCategoriesHint": "The vehicle was decoded but the catalog may not be ready yet. Reach out to support to expedite it.",
|
||||
"noAttrs": "No detailed info available for this vehicle.",
|
||||
"labelModel": "Model",
|
||||
"labelYear": "Model year",
|
||||
"labelEngineCode": "Engine code",
|
||||
"equipmentTitle": "Equipment codes ({count})",
|
||||
"showAll": "Show all ({count})",
|
||||
"showLess": "Show less",
|
||||
"categoryDetail": "Category detail",
|
||||
"categoryNotLoadedTitle": "Couldn't load category",
|
||||
"catalogUnavailableTitle": "Catalog isn't available right now",
|
||||
"catalogUnavailableHint": "Couldn't fetch this category from the supplier catalog. Please try again shortly."
|
||||
},
|
||||
"catalogSearch": {
|
||||
"placeholder": "Search the whole catalog — e.g. brake pads or OEM code",
|
||||
"ariaLabel": "Search the whole catalog",
|
||||
"clearAria": "Clear search",
|
||||
"searching": "Searching…",
|
||||
"treeIncomplete": "This vehicle's catalog hasn't been fully indexed yet. Search covers only categories opened so far — browsing more categories expands the scope.",
|
||||
"noMatch": "No categories or parts match \"{query}\".",
|
||||
"categoriesHeading": "Categories",
|
||||
"noCategoryMatch": "No matching categories.",
|
||||
"partsHeading": "Parts (OEM)",
|
||||
"noPartMatch": "No matching parts. (Only parts in opened categories are searched.)",
|
||||
"partsCount": "{count} part(s)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,5 +582,44 @@
|
||||
"pasteInvalidChars": "Şase numarasında I, O, Q harfleri kullanılamaz",
|
||||
"candidateSelectFailed": "Araç seçimi başarısız",
|
||||
"autoCorrected": "Otomatik düzeltildi: {list}"
|
||||
},
|
||||
"vehicle": {
|
||||
"title": "Araç",
|
||||
"back": "Geri dön",
|
||||
"notFoundTitle": "Araç bulunamadı",
|
||||
"errorTitle": "Araç yüklenemedi",
|
||||
"notFoundMsg": "Bu araç kaydı bulunamadı",
|
||||
"errorMsg": "Araç bilgileri yüklenemedi",
|
||||
"notFoundHint": "Bağlantı geçersiz olabilir ya da kayıt kaldırılmış olabilir.",
|
||||
"errorHint": "Veriler yüklenirken bir hata oluştu.",
|
||||
"retry": "Tekrar dene",
|
||||
"info": "Araç Bilgileri",
|
||||
"categories": "Yedek Parça Kategorileri",
|
||||
"noCategoriesTitle": "Bu araç için kategori bulunamadı",
|
||||
"noCategoriesHint": "Araç decode edildi ama parça kataloğu henüz hazırlanmamış olabilir. Destek ekibimize bildirirseniz katalog hızlandırılır.",
|
||||
"noAttrs": "Bu araç için ayrıntı bilgisi bulunamadı.",
|
||||
"labelModel": "Model",
|
||||
"labelYear": "Model yılı",
|
||||
"labelEngineCode": "Motor kodu",
|
||||
"equipmentTitle": "Donanım kodları ({count})",
|
||||
"showAll": "Tümünü göster ({count})",
|
||||
"showLess": "Daha az göster",
|
||||
"categoryDetail": "Kategori Detayı",
|
||||
"categoryNotLoadedTitle": "Kategori yüklenemedi",
|
||||
"catalogUnavailableTitle": "Katalog şu an yüklenemedi",
|
||||
"catalogUnavailableHint": "Bu kategori tedarikçi katalogundan alınamadı. Lütfen birazdan tekrar deneyin."
|
||||
},
|
||||
"catalogSearch": {
|
||||
"placeholder": "Tüm katalogda ara — ör. fren balatası veya OEM kodu",
|
||||
"ariaLabel": "Tüm katalogda ara",
|
||||
"clearAria": "Aramayı temizle",
|
||||
"searching": "Aranıyor…",
|
||||
"treeIncomplete": "Bu aracın kataloğu henüz tam indirilmemiş. Arama yalnızca şu ana kadar açılmış kategorileri kapsar — kategorileri gezdikçe kapsam genişler.",
|
||||
"noMatch": "\"{query}\" ile eşleşen kategori veya parça bulunamadı.",
|
||||
"categoriesHeading": "Kategoriler",
|
||||
"noCategoryMatch": "Eşleşen kategori yok.",
|
||||
"partsHeading": "Parçalar (OEM)",
|
||||
"noPartMatch": "Eşleşen parça yok. (Yalnızca açılmış kategorilerin parçaları aranır.)",
|
||||
"partsCount": "{count} parça"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { CategoryTree } from "@/components/categories/category-tree";
|
||||
import { CategoryViewToggle } from "@/components/categories/category-view-toggle";
|
||||
import { useCategoryParts } from "@/hooks/use-parts";
|
||||
import { api } from "@/lib/api-client";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import { getUserSettings, setUserSetting } from "@/lib/user-settings";
|
||||
import { cleanModelName } from "@/lib/vehicle";
|
||||
import { Button, Skeleton } from "@sase/ui";
|
||||
@@ -51,6 +52,7 @@ export const Route = createFileRoute("/dashboard/vehicles_/$id/categories_/$cate
|
||||
});
|
||||
|
||||
function VehicleCategoryPage() {
|
||||
const { t } = useTranslation();
|
||||
const { id, categoryId } = Route.useParams();
|
||||
const navigate = useNavigate();
|
||||
const { data, isLoading, error, refetch, isFetching } = useCategoryParts(id, categoryId);
|
||||
@@ -93,7 +95,7 @@ function VehicleCategoryPage() {
|
||||
const cleanModel = cleanModelName(vehicle?.model);
|
||||
const vehicleLabel = vehicle?.brandName
|
||||
? `${vehicle.brandName}${cleanModel ? ` ${cleanModel}` : ""}`
|
||||
: "Araç";
|
||||
: t("vehicle.title");
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@@ -111,8 +113,8 @@ function VehicleCategoryPage() {
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleBack}
|
||||
title="Geri dön"
|
||||
aria-label="Geri dön"
|
||||
title={t("vehicle.back")}
|
||||
aria-label={t("vehicle.back")}
|
||||
data-faro-user-action-name="category-back"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
@@ -123,7 +125,7 @@ function VehicleCategoryPage() {
|
||||
(isLoading ? (
|
||||
<span className="inline-block h-6 w-48 animate-pulse rounded-md bg-primary/10 align-middle" />
|
||||
) : (
|
||||
"Kategori Detayı"
|
||||
t("vehicle.categoryDetail")
|
||||
))}
|
||||
</h1>
|
||||
{data?.description && (
|
||||
@@ -141,9 +143,9 @@ function VehicleCategoryPage() {
|
||||
className="flex flex-col items-start gap-3 rounded-lg border border-destructive/40 bg-destructive/5 p-5 text-sm"
|
||||
>
|
||||
<div>
|
||||
<p className="font-medium text-destructive">Kategori yüklenemedi</p>
|
||||
<p className="font-medium text-destructive">{t("vehicle.categoryNotLoadedTitle")}</p>
|
||||
<p className="mt-1 text-muted-foreground">
|
||||
{error instanceof Error ? error.message : "Veriler yüklenirken bir hata oluştu."}
|
||||
{error instanceof Error ? error.message : t("vehicle.errorHint")}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
@@ -154,7 +156,7 @@ function VehicleCategoryPage() {
|
||||
disabled={isFetching}
|
||||
data-faro-user-action-name="category-retry"
|
||||
>
|
||||
{isFetching ? "Yükleniyor…" : "Tekrar dene"}
|
||||
{isFetching ? t("common.loading") : t("vehicle.retry")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -183,10 +185,8 @@ function VehicleCategoryPage() {
|
||||
className="flex flex-col items-start gap-3 rounded-lg border border-destructive/40 bg-destructive/5 p-5 text-sm"
|
||||
>
|
||||
<div>
|
||||
<p className="font-medium text-destructive">Katalog şu an yüklenemedi</p>
|
||||
<p className="mt-1 text-muted-foreground">
|
||||
Bu kategori tedarikçi katalogundan alınamadı. Lütfen birazdan tekrar deneyin.
|
||||
</p>
|
||||
<p className="font-medium text-destructive">{t("vehicle.catalogUnavailableTitle")}</p>
|
||||
<p className="mt-1 text-muted-foreground">{t("vehicle.catalogUnavailableHint")}</p>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -196,7 +196,7 @@ function VehicleCategoryPage() {
|
||||
disabled={isFetching}
|
||||
data-faro-user-action-name="category-loaderror-retry"
|
||||
>
|
||||
{isFetching ? "Yükleniyor…" : "Tekrar dene"}
|
||||
{isFetching ? t("common.loading") : t("vehicle.retry")}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -7,6 +7,7 @@ import { CategoryViewToggle } from "@/components/categories/category-view-toggle
|
||||
import { CarBrandLogo } from "@/components/ui/car-brand-logo";
|
||||
import { ApiError, api } from "@/lib/api-client";
|
||||
import { setChatwootVehicle } from "@/lib/chatwoot";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import { getUserSettings, setUserSetting } from "@/lib/user-settings";
|
||||
import { cleanModelName } from "@/lib/vehicle";
|
||||
import type { CategoryNode, Vehicle } from "@sase/shared";
|
||||
@@ -33,6 +34,7 @@ export const Route = createFileRoute("/dashboard/vehicles_/$id/")({
|
||||
});
|
||||
|
||||
function VehicleDetailPage() {
|
||||
const { t } = useTranslation();
|
||||
const { id } = Route.useParams();
|
||||
const [viewMode, setViewMode] = useState<"grid" | "tree" | "columns">(
|
||||
() => getUserSettings().categoryViewMode ?? "grid",
|
||||
@@ -81,7 +83,7 @@ function VehicleDetailPage() {
|
||||
|
||||
const vehicleLabel: string = vehicle?.brandName
|
||||
? `${vehicle.brandName}${cleanModelName(vehicle.model) ? ` ${cleanModelName(vehicle.model)}` : ""}`
|
||||
: "Araç";
|
||||
: t("vehicle.title");
|
||||
|
||||
// Surface the viewed vehicle to the support chat widget (VIN/brand/model)
|
||||
// so agents have the car context for part-compatibility questions.
|
||||
@@ -136,13 +138,15 @@ function VehicleDetailPage() {
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleBack}
|
||||
title="Geri dön"
|
||||
aria-label="Geri dön"
|
||||
title={t("vehicle.back")}
|
||||
aria-label={t("vehicle.back")}
|
||||
data-faro-user-action-name="vehicle-back"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<h2 className="text-xl font-bold">{is404 ? "Araç bulunamadı" : "Araç yüklenemedi"}</h2>
|
||||
<h2 className="text-xl font-bold">
|
||||
{is404 ? t("vehicle.notFoundTitle") : t("vehicle.errorTitle")}
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
role="alert"
|
||||
@@ -150,14 +154,14 @@ function VehicleDetailPage() {
|
||||
>
|
||||
<div>
|
||||
<p className="font-medium text-destructive">
|
||||
{is404 ? "Bu araç kaydı bulunamadı" : "Araç bilgileri yüklenemedi"}
|
||||
{is404 ? t("vehicle.notFoundMsg") : t("vehicle.errorMsg")}
|
||||
</p>
|
||||
<p className="mt-1 text-muted-foreground">
|
||||
{is404
|
||||
? "Bağlantı geçersiz olabilir ya da kayıt kaldırılmış olabilir."
|
||||
? t("vehicle.notFoundHint")
|
||||
: vehicleErrorObj instanceof Error
|
||||
? vehicleErrorObj.message
|
||||
: "Veriler yüklenirken bir hata oluştu."}
|
||||
: t("vehicle.errorHint")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
@@ -170,11 +174,11 @@ function VehicleDetailPage() {
|
||||
disabled={vehicleFetching}
|
||||
data-faro-user-action-name="vehicle-retry"
|
||||
>
|
||||
{vehicleFetching ? "Yükleniyor…" : "Tekrar dene"}
|
||||
{vehicleFetching ? t("common.loading") : t("vehicle.retry")}
|
||||
</Button>
|
||||
)}
|
||||
<Button type="button" size="sm" variant="ghost" onClick={handleBack}>
|
||||
Geri dön
|
||||
{t("vehicle.back")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -196,8 +200,8 @@ function VehicleDetailPage() {
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={handleBack}
|
||||
title="Geri dön"
|
||||
aria-label="Geri dön"
|
||||
title={t("vehicle.back")}
|
||||
aria-label={t("vehicle.back")}
|
||||
className="shrink-0"
|
||||
data-faro-user-action-name="vehicle-back"
|
||||
>
|
||||
@@ -222,7 +226,7 @@ function VehicleDetailPage() {
|
||||
<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>
|
||||
<span className="text-base font-semibold">{t("vehicle.info")}</span>
|
||||
<VehicleSummary vehicle={vehicle} />
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
@@ -237,7 +241,7 @@ function VehicleDetailPage() {
|
||||
{/* Categories */}
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle className="text-base">Yedek Parça Kategorileri</CardTitle>
|
||||
<CardTitle className="text-base">{t("vehicle.categories")}</CardTitle>
|
||||
{!searchActive && <CategoryViewToggle value={viewMode} onChange={changeViewMode} />}
|
||||
</CardHeader>
|
||||
<CardContent
|
||||
@@ -260,11 +264,10 @@ function VehicleDetailPage() {
|
||||
) : !categoryTree || categoryTree.length === 0 ? (
|
||||
<div className="rounded-lg border border-dashed border-border bg-muted/30 px-6 py-10 text-center">
|
||||
<p className="text-sm font-medium text-foreground">
|
||||
Bu araç için kategori bulunamadı
|
||||
{t("vehicle.noCategoriesTitle")}
|
||||
</p>
|
||||
<p className="mx-auto mt-1 max-w-md text-xs text-muted-foreground">
|
||||
Araç decode edildi ama parça kataloğu henüz hazırlanmamış olabilir. Destek
|
||||
ekibimize bildirirseniz katalog hızlandırılır.
|
||||
{t("vehicle.noCategoriesHint")}
|
||||
</p>
|
||||
</div>
|
||||
) : viewMode === "grid" ? (
|
||||
@@ -495,12 +498,11 @@ function getDisplayAttributes(vehicle: Vehicle): Attr[] {
|
||||
}
|
||||
|
||||
function VehicleAttributes({ vehicle }: { vehicle: Vehicle }) {
|
||||
const { t } = useTranslation();
|
||||
const attrs = getDisplayAttributes(vehicle);
|
||||
|
||||
if (attrs.length === 0) {
|
||||
return (
|
||||
<p className="text-sm text-muted-foreground">Bu araç için ayrıntı bilgisi bulunamadı.</p>
|
||||
);
|
||||
return <p className="text-sm text-muted-foreground">{t("vehicle.noAttrs")}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -536,10 +538,11 @@ function getEngineCode(vehicle: Vehicle): string | undefined {
|
||||
|
||||
/** Compact at-a-glance fields shown on the collapsed Araç Bilgileri header. */
|
||||
function VehicleSummary({ vehicle }: { vehicle: Vehicle }) {
|
||||
const { t } = useTranslation();
|
||||
const fields = [
|
||||
{ label: "Model", value: cleanModelName(vehicle.model) },
|
||||
{ label: "Model yılı", value: vehicle.year },
|
||||
{ label: "Motor kodu", value: getEngineCode(vehicle) },
|
||||
{ label: t("vehicle.labelModel"), value: cleanModelName(vehicle.model) },
|
||||
{ label: t("vehicle.labelYear"), value: vehicle.year },
|
||||
{ label: t("vehicle.labelEngineCode"), value: getEngineCode(vehicle) },
|
||||
].filter((f) => f.value != null && f.value !== "");
|
||||
|
||||
if (fields.length === 0) return null;
|
||||
@@ -585,6 +588,7 @@ function getEquipment(vehicle: Vehicle): Equipment[] {
|
||||
/** Full decoded equipment / PR-code list, shown inside the expanded card. */
|
||||
const EQUIPMENT_PREVIEW = 8;
|
||||
function VehicleEquipment({ vehicle }: { vehicle: Vehicle }) {
|
||||
const { t } = useTranslation();
|
||||
const items = getEquipment(vehicle);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
if (items.length === 0) return null;
|
||||
@@ -595,7 +599,7 @@ function VehicleEquipment({ vehicle }: { vehicle: Vehicle }) {
|
||||
return (
|
||||
<div className="mt-6 border-t border-border pt-4">
|
||||
<p className="mb-3 text-xs font-semibold text-muted-foreground">
|
||||
Donanım kodları ({items.length})
|
||||
{t("vehicle.equipmentTitle", { count: items.length })}
|
||||
</p>
|
||||
<ul className="grid grid-cols-1 gap-x-6 gap-y-2 sm:grid-cols-2">
|
||||
{visible.map((it, i) => (
|
||||
@@ -617,7 +621,7 @@ function VehicleEquipment({ vehicle }: { vehicle: Vehicle }) {
|
||||
onClick={() => setExpanded((v) => !v)}
|
||||
className="mt-3 h-8 px-2 text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{expanded ? "Daha az göster" : `Tümünü göster (${items.length})`}
|
||||
{expanded ? t("vehicle.showLess") : t("vehicle.showAll", { count: items.length })}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user