feat: service-test EMEX flow — candidate modal + vehicle card + auto-navigate
Some checks failed
CI / Lint, Typecheck, Test & Build (push) Has been cancelled
Some checks failed
CI / Lint, Typecheck, Test & Build (push) Has been cancelled
When EMEX returns candidates, show VehicleSelectModal for selection. When EMEX returns a single vehicle, show vehicle info card and auto-navigate to the category page via /vehicles/decode. Failed results still show JSON output as before. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
||||
import { Button, Input, Badge, Separator } from "@sase/ui";
|
||||
import {
|
||||
FlaskConical,
|
||||
@@ -8,9 +8,11 @@ import {
|
||||
AlertCircle,
|
||||
Copy,
|
||||
Check,
|
||||
Car,
|
||||
} from "lucide-react";
|
||||
import { api, ApiError } from "@/lib/api-client";
|
||||
import { toast } from "@/lib/toast";
|
||||
import { VehicleSelectModal } from "@/components/vehicles/vehicle-select-modal";
|
||||
|
||||
// ─── HELPERS ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -48,6 +50,7 @@ export const Route = createFileRoute("/dashboard/service-test")({
|
||||
});
|
||||
|
||||
function ServiceTestPage() {
|
||||
const navigate = useNavigate();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [vin, setVin] = useState("");
|
||||
const [service, setService] = useState("all");
|
||||
@@ -62,6 +65,11 @@ function ServiceTestPage() {
|
||||
} | null>(null);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
// EMEX candidate selection state
|
||||
const [candidates, setCandidates] = useState<any[] | null>(null);
|
||||
const [candidateVin, setCandidateVin] = useState("");
|
||||
const [selectLoading, setSelectLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
@@ -98,6 +106,33 @@ function ServiceTestPage() {
|
||||
vin: cleanVin,
|
||||
service: service === "all" ? undefined : service,
|
||||
});
|
||||
|
||||
// EMEX success with candidates → show selection modal
|
||||
if (data.success && data.result?.type === "candidates" && data.result.candidates) {
|
||||
setCandidateVin(cleanVin);
|
||||
setCandidates(data.result.candidates);
|
||||
setResult(data);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// EMEX success with single vehicle → navigate to decode flow
|
||||
if (data.success && data.result?.type === "vehicle" && data.result.vehicle) {
|
||||
setResult(data);
|
||||
setLoading(false);
|
||||
// Auto-decode via normal flow
|
||||
try {
|
||||
const decoded = await api.post<any>("/vehicles/decode", { vin: cleanVin });
|
||||
if (decoded.id) {
|
||||
navigate({ to: "/dashboard/vehicles/$id", params: { id: decoded.id } });
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// Fall through to show JSON result
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setResult(data);
|
||||
} catch (err) {
|
||||
const message = err instanceof ApiError ? err.message : "Bir hata oluştu. Lütfen tekrar deneyin.";
|
||||
@@ -120,6 +155,27 @@ function ServiceTestPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCandidateSelect(carId: string) {
|
||||
setSelectLoading(true);
|
||||
try {
|
||||
const data = await api.post<any>("/vehicles/decode", {
|
||||
vin: candidateVin,
|
||||
emexCarIndex: parseInt(carId, 10),
|
||||
});
|
||||
setCandidates(null);
|
||||
if (data.id) {
|
||||
navigate({ to: "/dashboard/vehicles/$id", params: { id: data.id } });
|
||||
}
|
||||
} catch (err) {
|
||||
const message = err instanceof ApiError ? err.message : "Bir hata oluştu.";
|
||||
setError(message);
|
||||
setCandidates(null);
|
||||
toast.error("Araç seçimi başarısız");
|
||||
} finally {
|
||||
setSelectLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function fillExampleVin() {
|
||||
setVin("WVWZZZ1JZ3W597935");
|
||||
inputRef.current?.focus();
|
||||
@@ -242,8 +298,47 @@ function ServiceTestPage() {
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* ─── SECTION 2: Result Card ──────────────────────────────────────── */}
|
||||
{result && (
|
||||
{/* ─── SECTION 2: Vehicle Info Card (successful single decode) ──── */}
|
||||
{result?.success && result.result?.type === "vehicle" && result.result.vehicle && (
|
||||
<div className="rounded-2xl border border-emerald-500/30 bg-background p-5 sm:p-6">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-emerald-500/10">
|
||||
<Car className="size-5 text-emerald-500" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="font-[family-name:var(--font-display)] text-lg font-bold">
|
||||
{result.result.vehicle.brand} {result.result.vehicle.model}
|
||||
</p>
|
||||
<p className="mt-0.5 text-sm text-muted-foreground">
|
||||
{result.result.vehicle.year || "—"}
|
||||
{result.result.vehicle.engineCode && ` — Motor: ${result.result.vehicle.engineCode}`}
|
||||
</p>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
<Badge variant="default" className="bg-emerald-600 text-xs text-white">
|
||||
Araç tanımlandı
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{result.service}
|
||||
</Badge>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{result.responseTimeMs.toLocaleString("tr-TR")}ms
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{result.result.vehicle.categories?.length > 0 && (
|
||||
<>
|
||||
<Separator className="my-4 bg-border" />
|
||||
<p className="mb-2 text-sm font-medium text-muted-foreground">
|
||||
{result.result.vehicle.categories.length} kategori bulundu
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ─── SECTION 3: Result Card (JSON for failures or non-vehicle results) ── */}
|
||||
{result && (!result.success || result.result?.type !== "vehicle") && result.result?.type !== "candidates" && (
|
||||
<div className="rounded-2xl border border-border bg-background p-5 sm:p-6">
|
||||
{/* Meta badges */}
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
@@ -297,6 +392,18 @@ function ServiceTestPage() {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ─── Vehicle Selection Modal (EMEX multi-result) ──────────────── */}
|
||||
{candidates && (
|
||||
<VehicleSelectModal
|
||||
open={!!candidates}
|
||||
onClose={() => setCandidates(null)}
|
||||
candidates={candidates}
|
||||
vin={candidateVin}
|
||||
onSelect={handleCandidateSelect}
|
||||
loading={selectLoading}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user