← back to Sku Check Skill

public/index.html

651 lines

<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="icon" type="image/svg+xml" href="/favicon.svg">

  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>SKU Check Skill - Designer Wallcoverings</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f1117; color: #e1e4e8; min-height: 100vh; }

    .agent-avatar { width: 56px; height: 56px; border-radius: 50%; border: 2px solid #EC4899; object-fit: cover; }
    .agent-avatar-fallback { width: 56px; height: 56px; border-radius: 50%; border: 2px solid #EC4899; background: #EC4899; color: #fff; font-size: 22px; font-weight: 700; display: none; align-items: center; justify-content: center; }

    .header { background: linear-gradient(135deg, #1a1f2e 0%, #0d1117 100%); border-bottom: 1px solid #30363d; padding: 16px 24px; display: flex; align-items: center; justify-content: space-between; }
    .header h1 { font-size: 20px; font-weight: 600; color: #58a6ff; }
    .header .subtitle { font-size: 13px; color: #8b949e; margin-top: 2px; }

    .container { max-width: 1600px; margin: 0 auto; padding: 20px; }

    .stats-bar { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin-bottom: 20px; }
    .stat-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 14px 16px; text-align: center; }
    .stat-card .value { font-size: 28px; font-weight: 700; }
    .stat-card .label { font-size: 12px; color: #8b949e; margin-top: 4px; text-transform: uppercase; letter-spacing: 0.5px; }
    .stat-card.found .value { color: #3fb950; }
    .stat-card.not-found .value { color: #f85149; }
    .stat-card.error .value { color: #d29922; }
    .stat-card.total .value { color: #58a6ff; }
    .stat-card.tagged .value { color: #bc8cff; }

    .panels { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; margin-bottom: 20px; }
    @media (max-width: 900px) { .panels { grid-template-columns: 1fr; } }
    .panel { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 16px; }
    .panel h3 { font-size: 14px; color: #58a6ff; margin-bottom: 12px; font-weight: 600; }
    .panel label { display: block; font-size: 12px; color: #8b949e; margin-bottom: 4px; }
    .panel input, .panel select { width: 100%; padding: 8px 10px; background: #0d1117; border: 1px solid #30363d; border-radius: 6px; color: #e1e4e8; font-size: 14px; margin-bottom: 10px; }
    .panel input:focus, .panel select:focus { border-color: #58a6ff; outline: none; }
    .row { display: flex; gap: 8px; }
    .row input { flex: 1; }

    .btn { padding: 6px 12px; border: none; border-radius: 5px; font-size: 12px; font-weight: 600; cursor: pointer; transition: all 0.15s; white-space: nowrap; }
    .btn-primary { background: #238636; color: #fff; }
    .btn-primary:hover { background: #2ea043; }
    .btn-danger { background: #da3633; color: #fff; }
    .btn-danger:hover { background: #f85149; }
    .btn-secondary { background: #30363d; color: #e1e4e8; }
    .btn-secondary:hover { background: #484f58; }
    .btn-sm { padding: 3px 8px; font-size: 11px; }
    .btn:disabled { opacity: 0.5; cursor: not-allowed; }

    /* Action button colors */
    .btn-specs { background: #0d9488; color: #fff; }
    .btn-specs:hover { background: #14b8a6; }
    .btn-images { background: #2563eb; color: #fff; }
    .btn-images:hover { background: #3b82f6; }
    .btn-ai { background: #7c3aed; color: #fff; }
    .btn-ai:hover { background: #8b5cf6; }
    .btn-tags { background: #d97706; color: #fff; }
    .btn-tags:hover { background: #f59e0b; }
    .btn-meta { background: #4f46e5; color: #fff; }
    .btn-meta:hover { background: #6366f1; }
    .btn-title { background: #16a34a; color: #fff; }
    .btn-title:hover { background: #22c55e; }
    .btn-sample { background: #db2777; color: #fff; }
    .btn-sample:hover { background: #ec4899; }
    .btn-all { background: #dc2626; color: #fff; font-weight: 700; }
    .btn-all:hover { background: #ef4444; }
    .btn-vendor { background: #0891b2; color: #fff; }
    .btn-vendor:hover { background: #06b6d4; }

    .job-bar { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 14px 16px; margin-bottom: 20px; display: none; }
    .job-bar.active { display: block; }
    .job-bar .job-title { font-size: 14px; font-weight: 600; color: #58a6ff; margin-bottom: 8px; }
    .progress-track { height: 6px; background: #21262d; border-radius: 3px; overflow: hidden; }
    .progress-fill { height: 100%; background: #238636; border-radius: 3px; transition: width 0.3s; }
    .job-stats { display: flex; gap: 16px; margin-top: 8px; font-size: 12px; color: #8b949e; }
    .job-stats span { display: flex; align-items: center; gap: 4px; }
    .dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
    .dot.green { background: #3fb950; }
    .dot.red { background: #f85149; }
    .dot.yellow { background: #d29922; }

    .filters { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; flex-wrap: wrap; }
    .filters select, .filters input { padding: 6px 10px; background: #0d1117; border: 1px solid #30363d; border-radius: 6px; color: #e1e4e8; font-size: 13px; }

    /* Bulk action bar */
    .bulk-bar { background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); border: 1px solid #334155; border-radius: 8px; padding: 12px 16px; margin-bottom: 12px; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
    .bulk-bar .bulk-label { font-size: 13px; font-weight: 600; color: #94a3b8; }

    .table-wrap { background: #161b22; border: 1px solid #30363d; border-radius: 8px; overflow-x: auto; }
    table { width: 100%; border-collapse: collapse; font-size: 13px; }
    thead th { background: #21262d; padding: 10px 12px; text-align: left; font-weight: 600; color: #8b949e; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid #30363d; white-space: nowrap; }
    tbody td { padding: 8px 10px; border-bottom: 1px solid #21262d; vertical-align: top; }
    tbody tr:hover { background: #1c2128; }
    .status-badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px; font-weight: 600; text-transform: uppercase; }
    .status-found { background: rgba(63, 185, 80, 0.15); color: #3fb950; }
    .status-not_found { background: rgba(248, 81, 73, 0.15); color: #f85149; }
    .status-error { background: rgba(210, 153, 34, 0.15); color: #d29922; }
    .status-pending { background: rgba(139, 148, 158, 0.15); color: #8b949e; }

    a { color: #58a6ff; text-decoration: none; }
    a:hover { text-decoration: underline; }

    .empty-state { text-align: center; padding: 40px 20px; color: #8b949e; }
    .empty-state h3 { font-size: 16px; margin-bottom: 8px; color: #e1e4e8; }

    /* Action button row */
    .action-btns { display: flex; gap: 3px; flex-wrap: wrap; max-width: 500px; }

    /* Detail panel that slides open below a row */
    .detail-row td { padding: 0 !important; border: none !important; }
    .detail-panel { background: #1c2128; border: 1px solid #30363d; border-radius: 0 0 8px 8px; margin: 0 8px 8px 8px; padding: 16px; display: none; }
    .detail-panel.open { display: block; animation: slideDown 0.2s ease-out; }
    @keyframes slideDown { from { opacity: 0; max-height: 0; } to { opacity: 1; max-height: 600px; } }
    .detail-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; }
    @media (max-width: 1200px) { .detail-grid { grid-template-columns: 1fr 1fr; } }
    .detail-section { background: #161b22; border: 1px solid #30363d; border-radius: 6px; padding: 12px; }
    .detail-section h4 { font-size: 12px; color: #58a6ff; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px; }
    .detail-section .field { display: flex; justify-content: space-between; padding: 3px 0; font-size: 12px; border-bottom: 1px solid #21262d; }
    .detail-section .field .fk { color: #8b949e; }
    .detail-section .field .fv { color: #e1e4e8; font-weight: 500; text-align: right; max-width: 200px; overflow: hidden; text-overflow: ellipsis; }
    .detail-images { display: flex; gap: 8px; flex-wrap: wrap; }
    .detail-images img { width: 80px; height: 80px; object-fit: cover; border-radius: 4px; border: 1px solid #30363d; cursor: pointer; }
    .detail-images img:hover { border-color: #58a6ff; }
    .ai-tag { display: inline-block; padding: 2px 6px; background: rgba(124, 58, 237, 0.2); color: #a78bfa; border-radius: 4px; font-size: 11px; margin: 2px; }
    .ai-color { display: inline-block; padding: 2px 6px; background: rgba(59, 130, 246, 0.2); color: #93c5fd; border-radius: 4px; font-size: 11px; margin: 2px; }
    .step-status { display: inline-block; font-size: 11px; margin-left: 4px; }
    .step-ok { color: #3fb950; }
    .step-fail { color: #f85149; }
    .step-spin { color: #d29922; }
    .spinner { display: inline-block; width: 12px; height: 12px; border: 2px solid #30363d; border-top-color: #58a6ff; border-radius: 50%; animation: spin 0.6s linear infinite; }
    @keyframes spin { to { transform: rotate(360deg); } }

    .toast { position: fixed; bottom: 20px; right: 20px; background: #238636; color: #fff; padding: 10px 20px; border-radius: 8px; font-size: 13px; display: none; z-index: 100; box-shadow: 0 4px 12px rgba(0,0,0,0.4); max-width: 400px; }
    .toast.error { background: #da3633; }
    .toast.show { display: block; animation: fadeIn 0.3s; }
    @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }

    .pagination { display: flex; justify-content: center; gap: 8px; margin-top: 12px; padding: 12px; }
  </style>
</head>
<body>
  <div class="header">
    <img class="agent-avatar" src="http://45.61.58.125/agent-avatars/the-stager.png" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'">
    <div class="agent-avatar-fallback">S</div>
    <div>
      <h1>SKU Check Skill</h1>
      <div class="subtitle">Cross-reference DW SKUs with vendor websites &bull; Fetch specs, AI analysis, update Shopify</div>
    </div>
    <div style="display: flex; gap: 8px;">
      <button class="btn btn-secondary btn-sm" onclick="loadStats()">Refresh Stats</button>
    </div>
  </div>

  <div class="container">
    <div class="stats-bar" id="statsBar">
      <div class="stat-card total"><div class="value" id="statTotal">0</div><div class="label">Total Checked</div></div>
      <div class="stat-card found"><div class="value" id="statFound">0</div><div class="label">Found</div></div>
      <div class="stat-card not-found"><div class="value" id="statNotFound">0</div><div class="label">Not Found</div></div>
      <div class="stat-card error"><div class="value" id="statErrors">0</div><div class="label">Errors</div></div>
      <div class="stat-card tagged"><div class="value" id="statTagged">0</div><div class="label">Tagged Review</div></div>
    </div>

    <div class="panels">
      <div class="panel">
        <h3>Check Single SKU</h3>
        <label>DW SKU (e.g. GGA-82668)</label>
        <input type="text" id="singleSku" placeholder="GGA-82668" />
        <button class="btn btn-primary" onclick="checkSingle()" id="btnSingle">Check SKU</button>
      </div>
      <div class="panel">
        <h3>Check Range</h3>
        <label>Prefix (e.g. GGA)</label>
        <input type="text" id="rangePrefix" placeholder="GGA" />
        <div class="row">
          <div><label>From</label><input type="number" id="rangeFrom" placeholder="82000" /></div>
          <div><label>To</label><input type="number" id="rangeTo" placeholder="83000" /></div>
        </div>
        <button class="btn btn-primary" onclick="checkRange()" id="btnRange">Check Range</button>
      </div>
      <div class="panel">
        <h3>Check by Vendor</h3>
        <label>Vendor</label>
        <select id="vendorSelect"><option value="">Loading vendors...</option></select>
        <label>Limit</label>
        <input type="number" id="vendorLimit" value="500" placeholder="500" />
        <button class="btn btn-primary" onclick="checkVendor()" id="btnVendor">Check Vendor</button>
      </div>
    </div>

    <div class="job-bar" id="jobBar">
      <div class="job-title" id="jobTitle">Processing...</div>
      <div class="progress-track"><div class="progress-fill" id="jobProgress" style="width:0%"></div></div>
      <div class="job-stats">
        <span><span class="dot green"></span> Found: <b id="jobFound">0</b></span>
        <span><span class="dot red"></span> Not Found: <b id="jobNotFound">0</b></span>
        <span><span class="dot yellow"></span> Errors: <b id="jobErrors">0</b></span>
        <span>Progress: <b id="jobChecked">0</b>/<b id="jobTotal">0</b></span>
        <button class="btn btn-danger btn-sm" onclick="stopJob()">Stop</button>
      </div>
    </div>

    <div class="filters">
      <select id="filterStatus" onchange="loadResults()">
        <option value="">All Statuses</option>
        <option value="found">Found</option>
        <option value="not_found">Not Found</option>
        <option value="error">Error</option>
      </select>
      <select id="filterVendor" onchange="loadResults()">
        <option value="">All Vendors</option>
      </select>
      <button class="btn btn-secondary btn-sm" onclick="loadResults()">Refresh</button>
    </div>

    <!-- Bulk actions -->
    <div class="bulk-bar">
      <span class="bulk-label">Bulk:</span>
      <button class="btn btn-all btn-sm" onclick="bulkRunAll()">Run ALL on Visible</button>
      <button class="btn btn-specs btn-sm" onclick="bulkAction('fetch-specs')">Fetch All Specs</button>
      <button class="btn btn-ai btn-sm" onclick="bulkAction('ai-analyze')">AI All</button>
      <button class="btn btn-tags btn-sm" onclick="bulkAction('update-tags')">Tags All</button>
      <button class="btn btn-meta btn-sm" onclick="bulkAction('update-metafields')">Meta All</button>
      <button class="btn btn-sample btn-sm" onclick="bulkAction('fix-sample')">Fix All Samples</button>
      <span id="bulkProgress" style="font-size:12px;color:#8b949e;margin-left:8px;"></span>
    </div>

    <div class="table-wrap">
      <table>
        <thead>
          <tr>
            <th>DW SKU</th>
            <th>MFR SKU</th>
            <th>Vendor</th>
            <th>Status</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody id="resultsBody">
          <tr><td colspan="5" class="empty-state"><h3>No results yet</h3>Check a SKU, range, or vendor to get started</td></tr>
        </tbody>
      </table>
    </div>

    <div class="pagination" id="pagination"></div>
  </div>

  <div class="toast" id="toast"></div>

  <script>
function xfetch(url,opts){return new Promise(function(resolve,reject){var x=new XMLHttpRequest();x.open((opts&&opts.method)||"GET",url,true);x.setRequestHeader("Authorization","Basic "+btoa("admin:DWSecure2024!"));if(opts&&opts.body){x.setRequestHeader("Content-Type","application/json");}x.onload=function(){try{resolve({ok:x.status>=200&&x.status<300,status:x.status,json:function(){return Promise.resolve(JSON.parse(x.responseText))},text:function(){return Promise.resolve(x.responseText)}})}catch(e){reject(e)}};x.onerror=function(){reject(new Error("XHR failed"))};x.send(opts&&opts.body?JSON.stringify(opts.body):null)})};
    const API = '';
    let currentPage = 0;
    const PAGE_SIZE = 50;
    let jobPollInterval = null;
    let currentResults = [];

    function showToast(msg, isError) {
      const t = document.getElementById('toast');
      t.textContent = msg;
      t.className = 'toast show' + (isError ? ' error' : '');
      setTimeout(() => t.className = 'toast', 4000);
    }

    async function api(method, path, body) {
      const opts = { method, headers: { 'Content-Type': 'application/json' } };
      if (body) opts.body = JSON.stringify(body);
      const r = await xfetch(API + path, opts);
      return r.json();
    }

    async function loadStats() {
      try {
        const data = await api('GET', '/api/stats');
        if (data.overall) {
          document.getElementById('statTotal').textContent = data.overall.total || 0;
          document.getElementById('statFound').textContent = data.overall.found || 0;
          document.getElementById('statNotFound').textContent = data.overall.not_found || 0;
          document.getElementById('statErrors').textContent = data.overall.errors || 0;
          document.getElementById('statTagged').textContent = data.overall.tagged || 0;
        }
      } catch (e) { console.error('Stats error:', e); }
    }

    async function loadVendors() {
      try {
        const data = await api('GET', '/api/vendors');
        const sel = document.getElementById('vendorSelect');
        const filter = document.getElementById('filterVendor');
        sel.innerHTML = '<option value="">Select vendor...</option>';
        data.forEach(v => {
          sel.innerHTML += `<option value="${v.vendor_code}">${v.vendor_name || v.vendor_code} (${v.sku_count})</option>`;
          filter.innerHTML += `<option value="${v.vendor_code}">${v.vendor_name || v.vendor_code}</option>`;
        });
      } catch (e) { console.error('Vendors error:', e); }
    }

    async function loadResults() {
      const status = document.getElementById('filterStatus').value;
      const vendor = document.getElementById('filterVendor').value;
      let qs = `?limit=${PAGE_SIZE}&offset=${currentPage * PAGE_SIZE}`;
      if (status) qs += `&status=${status}`;
      if (vendor) qs += `&vendor=${vendor}`;

      try {
        const data = await api('GET', '/api/results' + qs);
        currentResults = data.results || [];
        const tbody = document.getElementById('resultsBody');

        if (!data.results || data.results.length === 0) {
          tbody.innerHTML = '<tr><td colspan="5" class="empty-state"><h3>No results</h3>Check a SKU to get started</td></tr>';
          document.getElementById('pagination').innerHTML = '';
          return;
        }

        tbody.innerHTML = data.results.map((r, idx) => `
          <tr id="row-${r.dw_sku}">
            <td>
              <strong style="font-size:14px">${r.dw_sku_dash || r.dw_sku}</strong>
              ${r.shopify_handle ? `<br><a href="https://www.designerwallcoverings.com/products/${r.shopify_handle}" target="_blank" rel="noopener noreferrer" style="font-size:11px">View on site</a>` : ''}
              ${r.shopify_product_id ? `<br><a href="https://admin.shopify.com/store/designer-laboratory-sandbox/products/${r.shopify_product_id}" target="_blank" rel="noopener noreferrer" style="font-size:11px;color:#d29922">Admin</a>` : ''}
              ${r.vendor_url ? `<br><a href="${r.vendor_url}" target="_blank" rel="noopener noreferrer" style="font-size:11px;color:#0891b2" title="View ${r.mfr_sku} on manufacturer website">MFR Site</a>` : ''}
            </td>
            <td>
              <span style="font-weight:600">${r.mfr_sku || '-'}</span>
              <br><span style="font-size:11px;color:#8b949e">${r.checked_at ? new Date(r.checked_at).toLocaleDateString() : ''}</span>
            </td>
            <td>
              ${r.vendor_name || r.vendor_code || '-'}
              <br><a href="#" onclick="openVendorSite('${r.dw_sku}');return false" style="font-size:11px" class="btn-vendor-link">Vendor Site</a>
            </td>
            <td><span class="status-badge status-${r.check_status}">${r.check_status}</span></td>
            <td>
              <div class="action-btns">
                <button class="btn btn-specs btn-sm" onclick="doAction('fetch-specs','${r.dw_sku}')" title="Fetch specs from vendor catalog">Specs</button>
                <button class="btn btn-images btn-sm" onclick="doAction('fetch-images','${r.dw_sku}')" title="Fetch images from catalog">Imgs</button>
                <button class="btn btn-ai btn-sm" onclick="doAction('ai-analyze','${r.dw_sku}')" title="Run Gemini AI analysis">AI</button>
                <button class="btn btn-tags btn-sm" onclick="doAction('update-tags','${r.dw_sku}')" title="Update Shopify tags">Tags</button>
                <button class="btn btn-meta btn-sm" onclick="doAction('update-metafields','${r.dw_sku}')" title="Push metafields to Shopify">Meta</button>
                <button class="btn btn-title btn-sm" onclick="doAction('fix-title','${r.dw_sku}')" title="Fix title format">Title</button>
                <button class="btn btn-sample btn-sm" onclick="doAction('fix-sample','${r.dw_sku}')" title="Fix sample variant $4.25">Sample</button>
                <button class="btn btn-all btn-sm" onclick="doAction('run-all','${r.dw_sku}')" title="Run full pipeline">ALL</button>
                ${r.check_status === 'not_found' && !r.tagged_review ? `<button class="btn btn-danger btn-sm" onclick="tagReview('${r.dw_sku}')">Review</button>` : ''}
                ${r.tagged_review ? '<span style="color:#bc8cff;font-size:10px">Tagged</span>' : ''}
              </div>
              <div id="detail-${r.dw_sku}" class="detail-panel"></div>
            </td>
          </tr>
        `).join('');

        const totalPages = Math.ceil(data.total / PAGE_SIZE);
        const pag = document.getElementById('pagination');
        if (totalPages > 1) {
          let html = '';
          if (currentPage > 0) html += `<button class="btn btn-secondary btn-sm" onclick="goPage(${currentPage - 1})">Prev</button>`;
          html += `<span style="font-size:13px;color:#8b949e">Page ${currentPage + 1} of ${totalPages} (${data.total} total)</span>`;
          if (currentPage < totalPages - 1) html += `<button class="btn btn-secondary btn-sm" onclick="goPage(${currentPage + 1})">Next</button>`;
          pag.innerHTML = html;
        } else {
          pag.innerHTML = data.total > 0 ? `<span style="font-size:13px;color:#8b949e">${data.total} results</span>` : '';
        }
      } catch (e) { console.error('Results error:', e); }
    }

    function goPage(p) { currentPage = p; loadResults(); }

    // === ACTION HANDLER ===
    async function doAction(action, dwSku) {
      const panel = document.getElementById(`detail-${dwSku}`);
      panel.classList.add('open');
      panel.innerHTML = `<div style="text-align:center;padding:16px"><span class="spinner"></span> Running ${action}...</div>`;

      try {
        const data = await api('POST', `/api/action/${action}`, { dw_sku: dwSku });

        if (action === 'run-all') {
          panel.innerHTML = renderRunAllResult(data, dwSku);
        } else if (action === 'fetch-specs') {
          panel.innerHTML = renderSpecsResult(data);
        } else if (action === 'fetch-images') {
          panel.innerHTML = renderImagesResult(data);
        } else if (action === 'ai-analyze') {
          panel.innerHTML = renderAiResult(data);
        } else if (action === 'update-tags') {
          panel.innerHTML = renderTagsResult(data);
        } else if (action === 'update-metafields') {
          panel.innerHTML = renderMetaResult(data);
        } else if (action === 'fix-title') {
          panel.innerHTML = renderTitleResult(data);
        } else if (action === 'fix-sample') {
          panel.innerHTML = renderSampleResult(data);
        } else {
          panel.innerHTML = `<pre style="font-size:11px;color:#8b949e;white-space:pre-wrap">${JSON.stringify(data, null, 2)}</pre>`;
        }

        if (data.error) showToast(`${action}: ${data.error}`, true);
        else showToast(`${action} done for ${dwSku}`);
      } catch (e) {
        panel.innerHTML = `<div style="color:#f85149;padding:8px">Error: ${e.message}</div>`;
        showToast(e.message, true);
      }
    }

    function renderSpecsResult(data) {
      if (!data.specs) return `<div style="color:#f85149;padding:8px">${data.message || 'No specs found'}</div>`;
      const s = data.specs;
      const fields = [
        ['Pattern', s.patternName], ['Color', s.colorName], ['Collection', s.collection],
        ['Width', s.width], ['Length', s.length], ['Repeat', s.repeat],
        ['Composition', s.composition], ['Backing', s.backing], ['Fire Rating', s.fireRating],
        ['Match', s.matchType], ['Country', s.country],
        ['Retail', s.retailPrice], ['Trade', s.tradePrice], ['Cost', s.costPrice],
      ].filter(([,v]) => v);
      let html = `<div class="detail-section"><h4>Specs from Catalog</h4>`;
      fields.forEach(([k,v]) => { html += `<div class="field"><span class="fk">${k}</span><span class="fv">${v}</span></div>`; });
      if (s.productUrl) html += `<div style="margin-top:8px"><a href="${s.productUrl}" target="_blank" rel="noopener noreferrer" style="font-size:11px">View on vendor site</a></div>`;
      if (s.imageUrl) html += `<div style="margin-top:8px"><img src="${s.imageUrl}" style="max-width:120px;border-radius:4px;border:1px solid #30363d" /></div>`;
      html += `</div>`;
      return html;
    }

    function renderImagesResult(data) {
      if (!data.images || data.images.length === 0) return `<div style="color:#8b949e;padding:8px">${data.message || 'No images found'}</div>`;
      let html = `<div class="detail-section"><h4>Images (${data.images.length})</h4><div class="detail-images">`;
      data.images.forEach(url => { html += `<a href="${url}" target="_blank" rel="noopener noreferrer"><img src="${url}" /></a>`; });
      html += '</div>';
      if (data.productUrl) html += `<div style="margin-top:8px"><a href="${data.productUrl}" target="_blank" rel="noopener noreferrer" style="font-size:11px">Vendor product page</a></div>`;
      html += '</div>';
      return html;
    }

    function renderAiResult(data) {
      if (!data.analysis) return `<div style="color:#f85149;padding:8px">${data.message || 'AI analysis failed'}</div>`;
      const a = data.analysis;
      let html = `<div class="detail-section"><h4>AI Analysis (Gemini)</h4>`;
      html += `<div class="field"><span class="fk">Background</span><span class="fv">${a.backgroundColor || '-'}</span></div>`;
      html += `<div class="field"><span class="fk">Material</span><span class="fv">${a.material || '-'}</span></div>`;
      if (a.colors) html += `<div style="margin:6px 0"><span class="fk" style="font-size:11px">Colors: </span>${a.colors.map(c => `<span class="ai-color">${c}</span>`).join('')}</div>`;
      if (a.styles) html += `<div style="margin:4px 0"><span class="fk" style="font-size:11px">Styles: </span>${a.styles.map(s => `<span class="ai-tag">${s}</span>`).join('')}</div>`;
      if (a.patterns) html += `<div style="margin:4px 0"><span class="fk" style="font-size:11px">Patterns: </span>${a.patterns.map(p => `<span class="ai-tag">${p}</span>`).join('')}</div>`;
      if (a.tags) html += `<div style="margin:4px 0"><span class="fk" style="font-size:11px">Tags: </span>${a.tags.map(t => `<span class="ai-tag">${t}</span>`).join('')}</div>`;
      if (a.description) html += `<div style="margin-top:8px;font-size:12px;color:#d1d5db;line-height:1.4">${a.description}</div>`;
      html += '</div>';
      return html;
    }

    function renderTagsResult(data) {
      let html = `<div class="detail-section"><h4>Tags Updated</h4>`;
      if (data.error) { html += `<div style="color:#f85149">${data.error}</div>`; }
      else { html += `<div class="field"><span class="fk">Total Tags</span><span class="fv">${data.tagCount || 0}</span></div>`; }
      html += `<div style="margin-top:4px;font-size:11px;color:#8b949e">${data.message || ''}</div></div>`;
      return html;
    }

    function renderMetaResult(data) {
      let html = `<div class="detail-section"><h4>Metafields</h4>`;
      if (data.error) { html += `<div style="color:#f85149">${data.error}</div>`; }
      else { html += `<div class="field"><span class="fk">Set</span><span class="fv" style="color:#3fb950">${data.metafieldsSet}/${data.total}</span></div>`; }
      html += `<div style="margin-top:4px;font-size:11px;color:#8b949e">${data.message || ''}</div></div>`;
      return html;
    }

    function renderTitleResult(data) {
      let html = `<div class="detail-section"><h4>Title</h4>`;
      if (data.error) { html += `<div style="color:#f85149">${data.error}</div>`; }
      else {
        html += `<div class="field"><span class="fk">Old</span><span class="fv" style="color:#f85149">${data.oldTitle || '-'}</span></div>`;
        html += `<div class="field"><span class="fk">New</span><span class="fv" style="color:#3fb950">${data.newTitle || '-'}</span></div>`;
      }
      html += '</div>';
      return html;
    }

    function renderSampleResult(data) {
      let html = `<div class="detail-section"><h4>Sample Variant</h4>`;
      if (data.error) { html += `<div style="color:#f85149">${data.error}</div>`; }
      else {
        if (data.oldVariant) html += `<div class="field"><span class="fk">Was</span><span class="fv">${data.oldVariant.title} @ $${data.oldVariant.price}</span></div>`;
        if (data.newVariant) html += `<div class="field"><span class="fk">Now</span><span class="fv" style="color:#3fb950">${data.newVariant.title} @ $${data.newVariant.price} (${data.newVariant.sku})</span></div>`;
        html += `<div style="font-size:11px;color:#8b949e;margin-top:4px">${data.message || ''}</div>`;
      }
      html += '</div>';
      return html;
    }

    function renderRunAllResult(data, dwSku) {
      if (data.error) return `<div style="color:#f85149;padding:8px">${data.error}</div>`;
      const steps = data.steps || {};
      let html = `<div class="detail-grid">`;

      // Specs
      html += `<div class="detail-section"><h4>Specs ${steps.specs?.done ? '<span class="step-status step-ok">OK</span>' : '<span class="step-status step-fail">MISS</span>'}</h4>`;
      if (steps.specs?.data) {
        const s = steps.specs.data;
        [['Pattern', s.patternName], ['Color', s.colorName], ['Collection', s.collection], ['Width', s.width], ['Repeat', s.repeat]].filter(([,v]) => v).forEach(([k,v]) => {
          html += `<div class="field"><span class="fk">${k}</span><span class="fv">${v}</span></div>`;
        });
      } else { html += `<div style="color:#8b949e;font-size:11px">${steps.specs?.message || 'No data'}</div>`; }
      html += '</div>';

      // AI
      html += `<div class="detail-section"><h4>AI ${steps.ai?.done ? '<span class="step-status step-ok">OK</span>' : '<span class="step-status step-fail">MISS</span>'}</h4>`;
      if (steps.ai?.data) {
        const a = steps.ai.data;
        if (a.backgroundColor) html += `<div class="field"><span class="fk">BG</span><span class="fv">${a.backgroundColor}</span></div>`;
        if (a.colors) html += `<div style="margin:4px 0">${a.colors.map(c => `<span class="ai-color">${c}</span>`).join('')}</div>`;
        if (a.styles) html += `<div style="margin:4px 0">${a.styles.map(s => `<span class="ai-tag">${s}</span>`).join('')}</div>`;
        if (a.description) html += `<div style="font-size:11px;color:#d1d5db;margin-top:4px">${a.description.substring(0,150)}...</div>`;
      } else { html += `<div style="color:#8b949e;font-size:11px">${steps.ai?.message || 'No data'}</div>`; }
      html += '</div>';

      // Updates summary
      html += `<div class="detail-section"><h4>Updates</h4>`;
      html += `<div class="field"><span class="fk">Title</span><span class="fv">${steps.title?.done ? '<span class="step-ok">Updated</span>' : '<span class="step-fail">Failed</span>'}</span></div>`;
      html += `<div class="field"><span class="fk">Tags</span><span class="fv">${steps.tags?.count || 0} tags</span></div>`;
      html += `<div class="field"><span class="fk">Metafields</span><span class="fv">${steps.metafields?.set || 0}/${steps.metafields?.total || 0}</span></div>`;
      html += `<div class="field"><span class="fk">Sample</span><span class="fv">${steps.sample?.done ? '<span class="step-ok">OK</span>' : '<span class="step-fail">Failed</span>'}</span></div>`;
      if (steps.title?.newTitle) html += `<div style="font-size:11px;color:#3fb950;margin-top:6px">${steps.title.newTitle}</div>`;
      html += '</div>';

      html += '</div>';
      html += `<div style="text-align:center;margin-top:8px;font-size:12px;color:#3fb950;font-weight:600">${data.summary || 'Complete'}</div>`;
      return html;
    }

    // Open vendor website for a SKU
    async function openVendorSite(dwSku) {
      try {
        const data = await api('POST', '/api/action/vendor-url', { dw_sku: dwSku });
        if (data.searchUrl) window.open(data.searchUrl, '_blank');
        else if (data.vendorWebsite) window.open(data.vendorWebsite, '_blank');
        else showToast('No vendor URL found', true);
      } catch (e) { showToast(e.message, true); }
    }

    // Bulk actions
    async function bulkRunAll() {
      if (currentResults.length === 0) return showToast('No results to process', true);
      const prog = document.getElementById('bulkProgress');
      prog.textContent = `Processing 0/${currentResults.length}...`;
      for (let i = 0; i < currentResults.length; i++) {
        prog.textContent = `Processing ${i + 1}/${currentResults.length}...`;
        await doAction('run-all', currentResults[i].dw_sku);
        await new Promise(r => setTimeout(r, 500));
      }
      prog.textContent = `Done! ${currentResults.length} processed`;
      showToast(`Bulk Run ALL complete: ${currentResults.length} SKUs`);
    }

    async function bulkAction(action) {
      if (currentResults.length === 0) return showToast('No results to process', true);
      const prog = document.getElementById('bulkProgress');
      for (let i = 0; i < currentResults.length; i++) {
        prog.textContent = `${action} ${i + 1}/${currentResults.length}...`;
        await doAction(action, currentResults[i].dw_sku);
        await new Promise(r => setTimeout(r, 300));
      }
      prog.textContent = `Done! ${currentResults.length} processed`;
      showToast(`Bulk ${action} complete`);
    }

    // Existing functions
    async function checkSingle() {
      const sku = document.getElementById('singleSku').value.trim();
      if (!sku) return showToast('Enter a DW SKU', true);
      document.getElementById('btnSingle').disabled = true;
      document.getElementById('btnSingle').textContent = 'Checking...';
      try {
        const data = await api('POST', '/api/check/sku', { dw_sku: sku });
        if (data.error) showToast(data.error, true);
        else {
          const st = data.result?.check_status || data.vendorCheck?.status;
          showToast(`${sku}: ${st?.toUpperCase()} (MFR: ${data.crossref?.mfr_sku})`);
          loadResults(); loadStats();
        }
      } catch (e) { showToast('Error: ' + e.message, true); }
      document.getElementById('btnSingle').disabled = false;
      document.getElementById('btnSingle').textContent = 'Check SKU';
    }

    async function checkRange() {
      const prefix = document.getElementById('rangePrefix').value.trim();
      const from = parseInt(document.getElementById('rangeFrom').value);
      const to = parseInt(document.getElementById('rangeTo').value);
      if (!prefix || !from || !to) return showToast('Fill in prefix, from, and to', true);
      try {
        const data = await api('POST', '/api/check/range', { prefix, from, to });
        if (data.error) return showToast(data.error, true);
        showToast(`Job started: ${data.total} SKUs`);
        startJobPolling();
      } catch (e) { showToast('Error: ' + e.message, true); }
    }

    async function checkVendor() {
      const vendor_code = document.getElementById('vendorSelect').value;
      const limit = parseInt(document.getElementById('vendorLimit').value) || 500;
      if (!vendor_code) return showToast('Select a vendor', true);
      try {
        const data = await api('POST', '/api/check/vendor', { vendor_code, limit });
        if (data.error) return showToast(data.error, true);
        showToast(`Job started: ${data.total} SKUs`);
        startJobPolling();
      } catch (e) { showToast('Error: ' + e.message, true); }
    }

    function startJobPolling() {
      document.getElementById('jobBar').classList.add('active');
      if (jobPollInterval) clearInterval(jobPollInterval);
      jobPollInterval = setInterval(pollJob, 2000);
      pollJob();
    }

    async function pollJob() {
      try {
        const data = await api('GET', '/api/job');
        if (data.status === 'idle') { clearInterval(jobPollInterval); document.getElementById('jobBar').classList.remove('active'); loadResults(); loadStats(); return; }
        document.getElementById('jobTitle').textContent = `${data.type === 'vendor' ? 'Vendor' : 'Range'} Check: ${data.vendor_code || data.prefix || ''}`;
        document.getElementById('jobFound').textContent = data.found || 0;
        document.getElementById('jobNotFound').textContent = data.notFound || 0;
        document.getElementById('jobErrors').textContent = data.errors || 0;
        document.getElementById('jobChecked').textContent = data.checked || 0;
        document.getElementById('jobTotal').textContent = data.total || 0;
        document.getElementById('jobProgress').style.width = (data.total > 0 ? (data.checked / data.total * 100) : 0) + '%';
        if (data.status === 'complete' || data.status === 'stopped') {
          clearInterval(jobPollInterval);
          setTimeout(() => { document.getElementById('jobBar').classList.remove('active'); loadResults(); loadStats(); }, 3000);
          showToast(`Job ${data.status}: ${data.checked} checked, ${data.notFound} not found`);
        }
      } catch (e) {}
    }

    async function stopJob() { await api('POST', '/api/job/stop'); showToast('Job stop requested'); }

    async function tagReview(dwSku) {
      try {
        const data = await api('POST', '/api/tag-review', { dw_sku: dwSku });
        showToast(data.success ? `Tagged ${dwSku} as Review` : (data.error || data.message), !data.success);
        loadResults();
      } catch (e) { showToast('Error: ' + e.message, true); }
    }

    // Init
    loadStats();
    loadVendors();
    loadResults();
    setInterval(() => { loadStats(); }, 30000);
  </script>
</body>
</html>