feat(FN-094): add comment line for deployment verification
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
- Added a comment line to main.ts for deployment verification purposes
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { api } from "@/lib/api-client";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import { Button, Card, CardContent, CardHeader, CardTitle } from "@sase/ui";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
interface VariantItem {
|
||||
code: string;
|
||||
@@ -159,11 +159,7 @@ export function FordVariantSelector({ vehicleId, onSelect }: FordVariantSelector
|
||||
|
||||
{/* Proceed button — only enabled when all required dimensions are selected */}
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={handleProceed}
|
||||
disabled={!canProceed}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
<Button onClick={handleProceed} disabled={!canProceed} className="w-full sm:w-auto">
|
||||
{t("catalog.fordVariant.proceed")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
355
apps/web/src/components/catalog/model-list-columns.tsx
Normal file
355
apps/web/src/components/catalog/model-list-columns.tsx
Normal file
@@ -0,0 +1,355 @@
|
||||
import { api } from "@/lib/api-client";
|
||||
import { getCategoryIcon } from "@/lib/category-icons";
|
||||
import { cn } from "@sase/ui";
|
||||
import { Link, useNavigate } from "@tanstack/react-router";
|
||||
import { ArrowLeft, Car, ChevronRight, Loader2 } from "lucide-react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
/* ── Types ── */
|
||||
|
||||
interface CatalogVehicle {
|
||||
id: string;
|
||||
brandName: string;
|
||||
model: string;
|
||||
year: string | null;
|
||||
engine: string | null;
|
||||
bodyType: string | null;
|
||||
transmission: string | null;
|
||||
architecture: string | null;
|
||||
catalogPath: string | null;
|
||||
}
|
||||
|
||||
type ColumnItem =
|
||||
| { kind: "model"; id: string; label: string; sublabel?: string; vehicle: CatalogVehicle }
|
||||
| { kind: "restriction"; code: string; name: string; path: string; isFinal: boolean }
|
||||
| { kind: "category"; id: string; name: string; isLeaf: boolean; children?: any[] };
|
||||
|
||||
interface Column {
|
||||
type: "models" | "restrictions" | "categories";
|
||||
items: ColumnItem[];
|
||||
selectedId?: string;
|
||||
}
|
||||
|
||||
/* ── Component ── */
|
||||
|
||||
export function ModelListColumns({
|
||||
models,
|
||||
brandName,
|
||||
}: {
|
||||
models: CatalogVehicle[];
|
||||
brandName: string;
|
||||
}) {
|
||||
const navigate = useNavigate();
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const [loadingCol, setLoadingCol] = useState<number | null>(null);
|
||||
|
||||
// Track selected vehicle for category navigation and back button
|
||||
const selectedVehicleRef = useRef<string | null>(null);
|
||||
const selectedModelLabelRef = useRef<string | null>(null);
|
||||
const [inCategoryMode, setInCategoryMode] = useState(false);
|
||||
|
||||
const modelItems: ColumnItem[] = models.map((m) => ({
|
||||
kind: "model" as const,
|
||||
id: m.id,
|
||||
label: m.model,
|
||||
sublabel: [m.year, m.engine].filter(Boolean).join(" · ") || undefined,
|
||||
vehicle: m,
|
||||
}));
|
||||
|
||||
const [columns, setColumns] = useState<Column[]>([{ type: "models", items: modelItems }]);
|
||||
|
||||
// Reset when models change
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: modelItems is derived from models; gating on models prevents an extra effect when only modelItems identity changes
|
||||
useEffect(() => {
|
||||
setColumns([{ type: "models", items: modelItems }]);
|
||||
selectedVehicleRef.current = null;
|
||||
selectedModelLabelRef.current = null;
|
||||
setInCategoryMode(false);
|
||||
}, [models]);
|
||||
|
||||
const handleBackToModels = useCallback(() => {
|
||||
setColumns([{ type: "models", items: modelItems }]);
|
||||
setInCategoryMode(false);
|
||||
}, [modelItems]);
|
||||
|
||||
// Auto-scroll right when new column added
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: depending on columns.length is intentional — we only scroll when a column is added/removed, not on inner-item updates
|
||||
useEffect(() => {
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.scrollLeft = scrollRef.current.scrollWidth;
|
||||
}
|
||||
}, [columns.length]);
|
||||
|
||||
const truncateColumns = useCallback((fromIndex: number) => {
|
||||
setColumns((prev) => prev.slice(0, fromIndex + 1));
|
||||
}, []);
|
||||
|
||||
const setSelectedInColumn = useCallback((colIdx: number, itemId: string) => {
|
||||
setColumns((prev) =>
|
||||
prev.map((col, i) => (i === colIdx ? { ...col, selectedId: itemId } : col)),
|
||||
);
|
||||
}, []);
|
||||
|
||||
/* ── Handlers ── */
|
||||
|
||||
const handleModelSelect = useCallback(
|
||||
async (item: ColumnItem & { kind: "model" }, colIdx: number) => {
|
||||
const vehicle = item.vehicle;
|
||||
truncateColumns(colIdx);
|
||||
setSelectedInColumn(colIdx, item.id);
|
||||
selectedVehicleRef.current = vehicle.id;
|
||||
selectedModelLabelRef.current = item.label;
|
||||
|
||||
const arch = vehicle.architecture;
|
||||
const isLegacyVariant = ["LEGACY_PSA", "LEGACY_FORD", "LEGACY_VOLVO"].includes(arch ?? "");
|
||||
|
||||
if (isLegacyVariant) {
|
||||
// Navigate away — legacy brands need full-page variant selectors
|
||||
navigate({
|
||||
to: "/dashboard/catalog/$brandName/$modelId",
|
||||
params: { brandName, modelId: vehicle.id },
|
||||
search: { body: undefined, engine: undefined, gearbox: undefined, mgp: undefined },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const needsP5Restrictions =
|
||||
arch === "P5_MODERN" &&
|
||||
!!vehicle.catalogPath &&
|
||||
!vehicle.catalogPath.includes("/mainGroup");
|
||||
|
||||
setLoadingCol(colIdx);
|
||||
try {
|
||||
if (needsP5Restrictions) {
|
||||
// Fetch first restriction level
|
||||
const data = await api.get<{ options: any[]; isFinal: boolean }>(
|
||||
`/catalog/vehicles/${vehicle.id}/p5-restrictions`,
|
||||
);
|
||||
const items: ColumnItem[] = (data.options ?? []).map((o: any) => ({
|
||||
kind: "restriction" as const,
|
||||
code: o.code,
|
||||
name: o.name,
|
||||
path: o.path,
|
||||
isFinal: data.isFinal,
|
||||
}));
|
||||
setColumns((prev) => [...prev.slice(0, colIdx + 1), { type: "restrictions", items }]);
|
||||
} else {
|
||||
// Fetch categories directly
|
||||
await fetchCategories(vehicle.id, colIdx, undefined);
|
||||
}
|
||||
} catch {
|
||||
// On error, do nothing
|
||||
} finally {
|
||||
setLoadingCol(null);
|
||||
}
|
||||
},
|
||||
[brandName, navigate, truncateColumns, setSelectedInColumn],
|
||||
);
|
||||
|
||||
const handleRestrictionSelect = useCallback(
|
||||
async (item: ColumnItem & { kind: "restriction" }, colIdx: number) => {
|
||||
truncateColumns(colIdx);
|
||||
setSelectedInColumn(colIdx, item.code);
|
||||
|
||||
const vehicleId = selectedVehicleRef.current;
|
||||
if (!vehicleId) return;
|
||||
|
||||
setLoadingCol(colIdx);
|
||||
try {
|
||||
if (item.isFinal) {
|
||||
// Restrictions complete — fetch categories with mgp
|
||||
await fetchCategories(vehicleId, colIdx, item.path);
|
||||
} else {
|
||||
// More restriction levels
|
||||
const data = await api.get<{ options: any[]; isFinal: boolean }>(
|
||||
`/catalog/vehicles/${vehicleId}/p5-restrictions?path=${encodeURIComponent(item.path)}`,
|
||||
);
|
||||
const items: ColumnItem[] = (data.options ?? []).map((o: any) => ({
|
||||
kind: "restriction" as const,
|
||||
code: o.code,
|
||||
name: o.name,
|
||||
path: o.path,
|
||||
isFinal: data.isFinal,
|
||||
}));
|
||||
setColumns((prev) => [...prev.slice(0, colIdx + 1), { type: "restrictions", items }]);
|
||||
}
|
||||
} catch {
|
||||
// On error, do nothing
|
||||
} finally {
|
||||
setLoadingCol(null);
|
||||
}
|
||||
},
|
||||
[truncateColumns, setSelectedInColumn],
|
||||
);
|
||||
|
||||
const fetchCategories = useCallback(
|
||||
async (vehicleId: string, _afterColIdx: number, mgp: string | undefined) => {
|
||||
const mgpParam = mgp ? `?mgp=${encodeURIComponent(mgp)}` : "";
|
||||
const categories = await api.get<any[]>(
|
||||
`/catalog/vehicles/${vehicleId}/categories${mgpParam}`,
|
||||
);
|
||||
const items: ColumnItem[] = (categories ?? []).map((c: any) => ({
|
||||
kind: "category" as const,
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
isLeaf: c.children !== undefined && c.children.length === 0,
|
||||
children: c.children,
|
||||
}));
|
||||
// Reset columns — categories start fresh from leftmost column
|
||||
setColumns([{ type: "categories", items }]);
|
||||
setInCategoryMode(true);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleCategorySelect = useCallback(
|
||||
async (item: ColumnItem & { kind: "category" }, colIdx: number) => {
|
||||
truncateColumns(colIdx);
|
||||
setSelectedInColumn(colIdx, item.id);
|
||||
|
||||
const vehicleId = selectedVehicleRef.current;
|
||||
if (!vehicleId) return;
|
||||
|
||||
if (item.isLeaf) {
|
||||
// Navigate to schema page
|
||||
navigate({
|
||||
to: "/dashboard/catalog/$brandName/$modelId/categories/$categoryId",
|
||||
params: { brandName, modelId: vehicleId, categoryId: item.id },
|
||||
search: { body: undefined, engine: undefined, gearbox: undefined, mgp: undefined },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// If children already known from initial data
|
||||
if (item.children && item.children.length > 0) {
|
||||
const items: ColumnItem[] = item.children.map((c: any) => ({
|
||||
kind: "category" as const,
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
isLeaf: c.children !== undefined && c.children.length === 0,
|
||||
children: c.children,
|
||||
}));
|
||||
setColumns((prev) => [...prev.slice(0, colIdx + 1), { type: "categories", items }]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Lazy fetch children
|
||||
setLoadingCol(colIdx);
|
||||
try {
|
||||
const children = await api.get<any[]>(`/categories/${item.id}/children`);
|
||||
if (!children || children.length === 0) {
|
||||
// Actually a leaf — navigate
|
||||
navigate({
|
||||
to: "/dashboard/catalog/$brandName/$modelId/categories/$categoryId",
|
||||
params: { brandName, modelId: vehicleId, categoryId: item.id },
|
||||
search: { body: undefined, engine: undefined, gearbox: undefined, mgp: undefined },
|
||||
});
|
||||
return;
|
||||
}
|
||||
const items: ColumnItem[] = children.map((c: any) => ({
|
||||
kind: "category" as const,
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
isLeaf: c.children !== undefined && c.children.length === 0,
|
||||
children: c.children,
|
||||
}));
|
||||
setColumns((prev) => [...prev.slice(0, colIdx + 1), { type: "categories", items }]);
|
||||
} catch {
|
||||
// On error, do nothing
|
||||
} finally {
|
||||
setLoadingCol(null);
|
||||
}
|
||||
},
|
||||
[brandName, navigate, truncateColumns, setSelectedInColumn],
|
||||
);
|
||||
|
||||
const handleItemClick = useCallback(
|
||||
(item: ColumnItem, colIdx: number) => {
|
||||
if (item.kind === "model") handleModelSelect(item, colIdx);
|
||||
else if (item.kind === "restriction") handleRestrictionSelect(item, colIdx);
|
||||
else if (item.kind === "category") handleCategorySelect(item, colIdx);
|
||||
},
|
||||
[handleModelSelect, handleRestrictionSelect, handleCategorySelect],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{inCategoryMode && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleBackToModels}
|
||||
className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<ArrowLeft className="size-3.5" />
|
||||
<span>{selectedModelLabelRef.current ?? "Modellere Dön"}</span>
|
||||
</button>
|
||||
)}
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className="flex border rounded-lg overflow-x-auto"
|
||||
style={{ minHeight: 320 }}
|
||||
>
|
||||
{columns.map((col, colIdx) => (
|
||||
<div
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: columns are append/pop only — index is the stable identity here
|
||||
key={colIdx}
|
||||
className={cn(
|
||||
"w-[220px] shrink-0 overflow-y-auto",
|
||||
colIdx < columns.length - 1 && "border-r",
|
||||
)}
|
||||
style={{ maxHeight: 480 }}
|
||||
>
|
||||
{col.items.length === 0 ? (
|
||||
<div className="flex h-full items-center justify-center p-4 text-xs text-muted-foreground">
|
||||
Sonuç yok
|
||||
</div>
|
||||
) : (
|
||||
col.items.map((item) => {
|
||||
const itemId = item.kind === "restriction" ? item.code : item.id;
|
||||
const isSelected = col.selectedId === itemId;
|
||||
const isLoading = loadingCol === colIdx && isSelected;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={itemId}
|
||||
type="button"
|
||||
onClick={() => handleItemClick(item, colIdx)}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2 px-3 py-2 text-left text-sm transition-colors",
|
||||
"hover:bg-accent hover:text-accent-foreground",
|
||||
isSelected && "bg-accent text-accent-foreground font-medium",
|
||||
)}
|
||||
>
|
||||
<ItemIcon item={item} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="truncate">{item.kind === "model" ? item.label : item.name}</p>
|
||||
{item.kind === "model" && item.sublabel && (
|
||||
<p className="text-xs text-muted-foreground truncate">{item.sublabel}</p>
|
||||
)}
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<Loader2 className="size-3.5 shrink-0 animate-spin" />
|
||||
) : item.kind === "category" && item.isLeaf ? null : (
|
||||
<ChevronRight className="size-3.5 shrink-0 text-muted-foreground" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ItemIcon({ item }: { item: ColumnItem }) {
|
||||
if (item.kind === "model") {
|
||||
return <Car className="size-4 shrink-0 text-muted-foreground" />;
|
||||
}
|
||||
if (item.kind === "category") {
|
||||
const Icon = getCategoryIcon(item.name);
|
||||
return <Icon className="size-4 shrink-0 text-muted-foreground" />;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
46
apps/web/src/components/catalog/model-list-tree.tsx
Normal file
46
apps/web/src/components/catalog/model-list-tree.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { Car, ChevronRight } from "lucide-react";
|
||||
|
||||
interface CatalogVehicle {
|
||||
id: string;
|
||||
brandName: string;
|
||||
model: string;
|
||||
year: string | null;
|
||||
engine: string | null;
|
||||
bodyType: string | null;
|
||||
transmission: string | null;
|
||||
}
|
||||
|
||||
export function ModelListTree({
|
||||
models,
|
||||
brandName,
|
||||
}: {
|
||||
models: CatalogVehicle[];
|
||||
brandName: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="divide-y rounded-lg border">
|
||||
{models.map((model) => (
|
||||
<Link
|
||||
key={model.id}
|
||||
to="/dashboard/catalog/$brandName/$modelId"
|
||||
params={{ brandName, modelId: model.id }}
|
||||
search={{ body: undefined, engine: undefined, gearbox: undefined, mgp: undefined }}
|
||||
className="flex items-center gap-3 px-4 py-3 transition-colors hover:bg-accent"
|
||||
>
|
||||
<Car className="size-4 shrink-0 text-muted-foreground" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-medium truncate">{model.model}</p>
|
||||
<div className="flex flex-wrap gap-x-2 gap-y-0 text-xs text-muted-foreground">
|
||||
{model.year && <span>{model.year}</span>}
|
||||
{model.engine && <span>{model.engine}</span>}
|
||||
{model.bodyType && <span>{model.bodyType}</span>}
|
||||
{model.transmission && <span>{model.transmission}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<ChevronRight className="size-4 shrink-0 text-muted-foreground" />
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
113
apps/web/src/components/catalog/p5-restriction-selector.tsx
Normal file
113
apps/web/src/components/catalog/p5-restriction-selector.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { api } from "@/lib/api-client";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@sase/ui";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ArrowLeft, Loader2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
interface RestrictionOption {
|
||||
code: string;
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
interface P5RestrictionsResponse {
|
||||
options: RestrictionOption[];
|
||||
isFinal: boolean;
|
||||
}
|
||||
|
||||
interface P5RestrictionSelectorProps {
|
||||
vehicleId: string;
|
||||
onComplete: (mainGroupsPath: string) => void;
|
||||
}
|
||||
|
||||
export function P5RestrictionSelector({ vehicleId, onComplete }: P5RestrictionSelectorProps) {
|
||||
const [steps, setSteps] = useState<
|
||||
Array<{ label: string; selectedCode: string; selectedPath: string }>
|
||||
>([]);
|
||||
const [currentPath, setCurrentPath] = useState<string | undefined>(undefined);
|
||||
|
||||
const { data, isLoading } = useQuery<P5RestrictionsResponse>({
|
||||
queryKey: ["p5-restrictions", vehicleId, currentPath ?? "initial"],
|
||||
queryFn: () => {
|
||||
const pathParam = currentPath ? `?path=${encodeURIComponent(currentPath)}` : "";
|
||||
return api.get<P5RestrictionsResponse>(
|
||||
`/catalog/vehicles/${vehicleId}/p5-restrictions${pathParam}`,
|
||||
);
|
||||
},
|
||||
enabled: !!vehicleId,
|
||||
});
|
||||
|
||||
const options = data?.options ?? [];
|
||||
|
||||
const handleSelect = (option: RestrictionOption) => {
|
||||
if (data?.isFinal) {
|
||||
// This is the last selection step — option.path leads to mainGroups
|
||||
onComplete(option.path);
|
||||
} else {
|
||||
// More levels needed — advance to next restriction
|
||||
setSteps((prev) => [
|
||||
...prev,
|
||||
{ label: option.name, selectedCode: option.code, selectedPath: option.path },
|
||||
]);
|
||||
setCurrentPath(option.path);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
if (steps.length === 0) return;
|
||||
const newSteps = steps.slice(0, -1);
|
||||
setSteps(newSteps);
|
||||
setCurrentPath(newSteps.length > 0 ? newSteps[newSteps.length - 1].selectedPath : undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Araç Konfigürasyonu</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{steps.length > 0 && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleBack}
|
||||
className="flex items-center gap-1 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<ArrowLeft className="size-3" />
|
||||
Geri
|
||||
</button>
|
||||
<span className="text-muted-foreground">|</span>
|
||||
{steps.map((step, i) => (
|
||||
<span key={step.selectedCode} className="text-muted-foreground">
|
||||
{i > 0 && " / "}
|
||||
<span className="font-medium text-foreground">{step.label}</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<Loader2 className="size-4 animate-spin" />
|
||||
Seçenekler yükleniyor...
|
||||
</div>
|
||||
) : options.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">Seçenek bulunamadı</p>
|
||||
) : (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{options.map((option) => (
|
||||
<button
|
||||
key={option.code}
|
||||
type="button"
|
||||
onClick={() => handleSelect(option)}
|
||||
className="rounded-md border border-border bg-background px-3 py-1.5 text-sm transition-colors hover:bg-accent hover:border-accent-foreground/20"
|
||||
>
|
||||
{option.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { api } from "@/lib/api-client";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import { Button, Card, CardContent, CardHeader, CardTitle } from "@sase/ui";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
interface VariantItem {
|
||||
code: string;
|
||||
@@ -32,7 +32,7 @@ export function PsaVariantSelector({ vehicleId, onSelect }: PsaVariantSelectorPr
|
||||
queryKey: ["psa-engines", vehicleId, selectedBody],
|
||||
queryFn: () =>
|
||||
api.get<VariantItem[]>(
|
||||
`/catalog/vehicles/${vehicleId}/psa-engines?body=${encodeURIComponent(selectedBody!)}`,
|
||||
`/catalog/vehicles/${vehicleId}/psa-engines?body=${encodeURIComponent(selectedBody ?? "")}`,
|
||||
),
|
||||
enabled: !!vehicleId && !!selectedBody,
|
||||
});
|
||||
@@ -41,7 +41,7 @@ export function PsaVariantSelector({ vehicleId, onSelect }: PsaVariantSelectorPr
|
||||
queryKey: ["psa-gearboxes", vehicleId, selectedBody, selectedEngine],
|
||||
queryFn: () =>
|
||||
api.get<VariantItem[]>(
|
||||
`/catalog/vehicles/${vehicleId}/psa-gearboxes?body=${encodeURIComponent(selectedBody!)}&engine=${encodeURIComponent(selectedEngine!)}`,
|
||||
`/catalog/vehicles/${vehicleId}/psa-gearboxes?body=${encodeURIComponent(selectedBody ?? "")}&engine=${encodeURIComponent(selectedEngine ?? "")}`,
|
||||
),
|
||||
enabled: !!vehicleId && !!selectedBody && !!selectedEngine,
|
||||
});
|
||||
@@ -58,7 +58,8 @@ export function PsaVariantSelector({ vehicleId, onSelect }: PsaVariantSelectorPr
|
||||
|
||||
const handleEngineSelect = (code: string | "_all_") => {
|
||||
if (code === "_all_") {
|
||||
onSelect(selectedBody!, "_all_", "_all_");
|
||||
if (!selectedBody) return;
|
||||
onSelect(selectedBody, "_all_", "_all_");
|
||||
return;
|
||||
}
|
||||
setSelectedEngine(code);
|
||||
@@ -67,7 +68,8 @@ export function PsaVariantSelector({ vehicleId, onSelect }: PsaVariantSelectorPr
|
||||
|
||||
const handleGearboxSelect = (code: string | "_all_") => {
|
||||
if (code === "_all_") {
|
||||
onSelect(selectedBody!, selectedEngine!, "_all_");
|
||||
if (!selectedBody || !selectedEngine) return;
|
||||
onSelect(selectedBody, selectedEngine, "_all_");
|
||||
return;
|
||||
}
|
||||
setSelectedGearbox(code);
|
||||
|
||||
Reference in New Issue
Block a user