feat: EMEX HTTP decode + multi-candidate selection UI
Replace Playwright browser with pure HTTP for EMEX VIN decode:
- Vehicles.aspx, QuickGroups.aspx, QuickDetails.aspx all work
without authentication — no session/browser needed
- VIN decode drops from ~10-20s (Playwright) to ~1-5s (HTTP)
- Playwright browser kept as fallback if HTTP fails
Add multi-candidate selection for EMEX (mirrors PartsCatalogs flow):
- EmexCandidate interface matches PcatCar shape so VehicleSelectModal
renders without changes
- decodeVinOrCandidates(): single HTTP fetch returns vehicle OR
candidates (avoids double Vehicles.aspx request)
- decodeVinByIndex(): resolves user-selected candidate by index
- vehicles.service: emexCandidates in VinResolveResult, emexCarIndex
param in decodeVin/resolveVin, resolveEmexCarByIndex() method
- vehicles.controller: @Body("emexCarIndex") accepted
- search.tsx: candidateSource state, handleCandidateSelect sends
emexCarIndex or pcatCarId based on source
Test VIN NM417800006410193 (Fiat SIENA) now shows 2-option modal.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -52,9 +52,10 @@ function SearchPage() {
|
||||
const [reportSending, setReportSending] = useState(false);
|
||||
const [reportSent, setReportSent] = useState(false);
|
||||
|
||||
// Vehicle candidate selection (PartsCatalogs multi-result)
|
||||
// Vehicle candidate selection (PartsCatalogs or EMEX multi-result)
|
||||
const [candidates, setCandidates] = useState<any[] | null>(null);
|
||||
const [candidateVin, setCandidateVin] = useState("");
|
||||
const [candidateSource, setCandidateSource] = useState<"parts-catalogs" | "emex" | null>(null);
|
||||
const [selectLoading, setSelectLoading] = useState(false);
|
||||
|
||||
// Live preview state
|
||||
@@ -149,13 +150,15 @@ function SearchPage() {
|
||||
try {
|
||||
const data = await api.post<any>("/vehicles/decode", { vin: cleanVin });
|
||||
|
||||
// Handle multiple vehicle candidates (PartsCatalogs)
|
||||
// Handle multiple vehicle candidates (PartsCatalogs or EMEX)
|
||||
if (data.candidates && Array.isArray(data.candidates)) {
|
||||
setCandidates(data.candidates);
|
||||
setCandidateVin(cleanVin);
|
||||
setCandidateSource(data.source ?? "parts-catalogs");
|
||||
capture("vin_decode_candidates", {
|
||||
vin: cleanVin,
|
||||
count: data.candidates.length,
|
||||
source: data.source,
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
@@ -209,19 +212,24 @@ function SearchPage() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCandidateSelect(pcatCarId: string) {
|
||||
async function handleCandidateSelect(carId: string) {
|
||||
setSelectLoading(true);
|
||||
try {
|
||||
const data = await api.post<any>("/vehicles/decode", {
|
||||
vin: candidateVin,
|
||||
pcatCarId,
|
||||
});
|
||||
const payload: Record<string, unknown> = { vin: candidateVin };
|
||||
if (candidateSource === "emex") {
|
||||
payload.emexCarIndex = parseInt(carId, 10);
|
||||
} else {
|
||||
payload.pcatCarId = carId;
|
||||
}
|
||||
const data = await api.post<any>("/vehicles/decode", payload);
|
||||
capture("vin_decode_candidate_selected", {
|
||||
vin: candidateVin,
|
||||
pcatCarId,
|
||||
source: candidateSource,
|
||||
carId,
|
||||
vehicle_id: data.id,
|
||||
});
|
||||
setCandidates(null);
|
||||
setCandidateSource(null);
|
||||
navigate({
|
||||
to: "/dashboard/vehicles/$id",
|
||||
params: { id: data.id },
|
||||
@@ -233,6 +241,7 @@ function SearchPage() {
|
||||
: "Bir hata oluştu. Lütfen tekrar deneyin.";
|
||||
setError(message);
|
||||
setCandidates(null);
|
||||
setCandidateSource(null);
|
||||
toast.error("Araç seçimi başarısız");
|
||||
} finally {
|
||||
setSelectLoading(false);
|
||||
@@ -474,7 +483,7 @@ function SearchPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ─── Vehicle Selection Modal (PartsCatalogs multi-result) ──── */}
|
||||
{/* ─── Vehicle Selection Modal (PartsCatalogs / EMEX multi-result) ──── */}
|
||||
{candidates && (
|
||||
<VehicleSelectModal
|
||||
open={!!candidates}
|
||||
|
||||
Reference in New Issue
Block a user