"use strict"; const form = document.getElementById("fetchForm"); const fetchBtn = document.getElementById("fetchBtn"); const cardsContainer = document.getElementById("cardsContainer"); // Remove empty state on first card function clearEmpty() { const empty = cardsContainer.querySelector(".empty-state"); if (empty) empty.remove(); } // ── Create a new VIN card ────────────────────────────────────────────────────── function createCard(vin) { clearEmpty(); const safeVin = vin.replace(/[^A-Z0-9]/g, ""); const card = document.createElement("div"); card.className = "vin-card"; card.id = `card-${safeVin}`; // Build DOM without innerHTML interpolation to prevent XSS const header = document.createElement("div"); header.className = "card-header"; const headerLeft = document.createElement("div"); const vinEl = document.createElement("div"); vinEl.className = "card-vin"; vinEl.textContent = safeVin; const vehicleEl = document.createElement("div"); vehicleEl.className = "card-vehicle"; vehicleEl.id = `vehicle-${safeVin}`; vehicleEl.textContent = "Consultando..."; headerLeft.appendChild(vinEl); headerLeft.appendChild(vehicleEl); const dismissBtn = document.createElement("button"); dismissBtn.className = "card-dismiss"; dismissBtn.title = "Cerrar"; dismissBtn.textContent = "✕"; dismissBtn.addEventListener("click", () => card.remove()); header.appendChild(headerLeft); header.appendChild(dismissBtn); const body = document.createElement("div"); body.className = "card-body"; const logEl = document.createElement("div"); logEl.className = "progress-log"; logEl.id = `log-${safeVin}`; const actionsEl = document.createElement("div"); actionsEl.id = `actions-${safeVin}`; body.appendChild(logEl); body.appendChild(actionsEl); card.appendChild(header); card.appendChild(body); cardsContainer.prepend(card); return card; } // ── Add a log line to a card ─────────────────────────────────────────────────── function addLog(vin, text, status) { const log = document.getElementById(`log-${vin}`); if (!log) return; // Remove spinner from any previous progress item if (status !== "progress") { const prev = log.querySelector(".log-item.progress"); if (prev) prev.classList.replace("progress", "done"); } const item = document.createElement("div"); item.className = `log-item ${status}`; if (status === "progress") { item.innerHTML = `${text}`; } else { item.textContent = text; } log.appendChild(item); item.scrollIntoView({ behavior: "smooth", block: "nearest" }); } // ── Show card actions after fetch completes ─────────────────────────────────── function showActions(vin, risk) { const actionsDiv = document.getElementById(`actions-${vin}`); if (!actionsDiv) return; // Risk badge const badge = document.createElement("div"); badge.className = `risk-badge ${risk.color}`; badge.textContent = `${risk.emoji} ${risk.score}/100 — ${risk.level}`; actionsDiv.appendChild(badge); // Buttons row const row = document.createElement("div"); row.className = "card-actions"; const genBtn = document.createElement("button"); genBtn.className = "btn btn-gen"; genBtn.textContent = "📄 Generar PDF"; genBtn.addEventListener("click", () => generatePDF(vin, genBtn)); row.appendChild(genBtn); actionsDiv.appendChild(row); } // ── Generate PDF ────────────────────────────────────────────────────────────── async function generatePDF(vin, btn) { btn.disabled = true; btn.textContent = "⏳ Generando..."; try { const res = await fetch("/api/generate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ vin }), }); const data = await res.json(); if (data.error) { btn.disabled = false; btn.textContent = "📄 Generar PDF"; addLog(vin, `❌ ${data.error}`, "error"); return; } btn.textContent = "✅ PDF listo"; const actionsDiv = document.getElementById(`actions-${vin}`); // Download link const link = document.createElement("a"); link.href = data.url; link.download = data.filename; link.className = "pdf-link"; link.textContent = `⬇️ ${data.filename}`; actionsDiv.appendChild(link); // Open locally button (Windows only via server) const openBtn = document.createElement("button"); openBtn.className = "btn btn-open"; openBtn.textContent = "🗂️ Abrir localmente"; openBtn.addEventListener("click", () => openLocally(data.filename, openBtn)); actionsDiv.querySelector(".card-actions").appendChild(openBtn); addLog(vin, `✅ PDF generado: ${data.filename}`, "success"); } catch (e) { btn.disabled = false; btn.textContent = "📄 Generar PDF"; addLog(vin, `❌ Error al generar PDF: ${e.message}`, "error"); } } // ── Open file locally via server ────────────────────────────────────────────── async function openLocally(filename, btn) { btn.disabled = true; try { const res = await fetch("/api/open", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ filename }), }); const data = await res.json(); if (data.error) { alert(data.error); } } catch (e) { alert("Error al abrir: " + e.message); } finally { btn.disabled = false; } } // ── Form submission → SSE ────────────────────────────────────────────────────── form.addEventListener("submit", async (e) => { e.preventDefault(); const fd = new FormData(form); const vin = (fd.get("vin") || "").trim().toUpperCase(); if (!vin) return; // Prevent duplicate active cards const existing = document.getElementById(`card-${vin}`); if (existing) { existing.scrollIntoView({ behavior: "smooth" }); existing.style.outline = "2px solid var(--accent)"; setTimeout(() => (existing.style.outline = ""), 1500); return; } fetchBtn.disabled = true; fetchBtn.textContent = "⏳ Consultando..."; createCard(vin); const params = new URLSearchParams({ vin, odometer: fd.get("odometer") || "", primary_damage: fd.get("primary_damage") || "", secondary_damage: fd.get("secondary_damage") || "", title: fd.get("title") || "", bid: fd.get("bid") || "", auction: fd.get("auction") || "", photo_url: fd.get("photo_url") || "", }); const es = new EventSource(`/api/fetch?${params.toString()}`); es.onmessage = (event) => { let msg; try { msg = JSON.parse(event.data); } catch { return; } const status = msg.status || "progress"; if (status === "done") { const vehicleEl = document.getElementById(`vehicle-${vin}`); if (vehicleEl && msg.vehicle) vehicleEl.textContent = msg.vehicle; showActions(vin, msg.risk); es.close(); fetchBtn.disabled = false; fetchBtn.textContent = "⚡ FETCH DATA"; return; } if (status === "error") { addLog(vin, msg.step, "error"); es.close(); fetchBtn.disabled = false; fetchBtn.textContent = "⚡ FETCH DATA"; return; } if (status === "success" && msg.vehicle) { const vehicleEl = document.getElementById(`vehicle-${vin}`); if (vehicleEl) vehicleEl.textContent = msg.vehicle; } addLog(vin, msg.step, status); }; es.onerror = () => { addLog(vin, "❌ Conexión interrumpida con el servidor", "error"); es.close(); fetchBtn.disabled = false; fetchBtn.textContent = "⚡ FETCH DATA"; }; });