Harshit Pathak 3 tháng trước cách đây
mục cha
commit
b128eebb80

+ 124 - 0
content_quality_tool_public/static/css/attr-extraction.css

@@ -0,0 +1,124 @@
+:root {
+    --text: #0b1220;           /* primary text on white */
+    --muted: #5b6b80;          /* secondary text */
+    --brand: #2563eb;          /* blue */
+    --brand-2: #14b8a6;        /* teal */
+    --accent: #7c3aed;         /* violet */
+    --danger: #ef4444;         /* red */
+    --success: #16a34a;        /* green */
+    --chip: #f5f7fb;           /* chip bg */
+    --border: #e5eaf2;         /* light border */
+    --shadow: 0 8px 24px rgba(17, 24, 39, .08);
+    --radius: 14px;
+    --card: #ffffff;           /* card bg on white */
+    --row-hover: #e6effd;      /* subtle hover border tint */
+    --zebra: #fafbfc;
+}
+
+* { box-sizing: border-box; }
+html, body { height: 100%; }
+body {
+    margin: 0;
+    font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji";
+    color: var(--text);
+    background: #ffffff; /* White background */
+    letter-spacing: .2px;
+}
+
+.container { max-width: 1200px; margin: 24px auto 80px; padding: 0 20px; }
+
+header.hero { display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: center; margin-bottom: 20px; }
+.title { display: flex; align-items: center; gap: 14px; }
+.title-logo { width: 44px; height: 44px; border-radius: 12px; background: linear-gradient(135deg, var(--brand), var(--accent)); display: grid; place-items: center; box-shadow: var(--shadow); }
+.title-logo svg { filter: drop-shadow(0 2px 6px rgba(0,0,0,.15)); }
+h1 { font-size: 1.6rem; margin: 0; letter-spacing: .3px; }
+.sub { color: var(--muted); font-size: .95rem; margin-top: 4px; }
+
+.actions { display: flex; gap: 10px; flex-wrap: wrap; }
+button, .btn {
+    appearance: none; border: 1px solid var(--border); cursor: pointer;
+    padding: 10px 14px; border-radius: 10px; font-weight: 600;
+    color: var(--text); background: #ffffff; transition: transform .06s ease, box-shadow .2s ease, border-color .2s ease;
+    box-shadow: var(--shadow);
+}
+.btn:hover{
+    color: black !important;
+}
+button:hover { transform: translateY(-1px); border-color: #cbd5e1; }
+.btn-secondary { background: #f7fafc; }
+.btn-ghost { background: transparent; color: var(--muted); border-style: dashed; }
+.btn-success { background: linear-gradient(135deg, #eef2ff, #e6fffa); color: var(--text); }
+
+/* Single-column layout */
+.grid { display: grid; grid-template-columns: 1fr; gap: 18px; }
+
+.card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
+.card h2 { margin: 0; font-size: 1.1rem; }
+.card-header { padding: 14px 16px; border-bottom: 1px solid var(--border); display:flex; align-items:center; justify-content:space-between; gap:10px; }
+.card-body { padding: 14px 16px; }
+
+.toolbar { display:flex; align-items:center; gap:10px; flex-wrap: wrap; }
+.pill { display: inline-flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 999px; background: var(--chip); color: #2e3a49; border: 1px solid var(--border); font-size: .85rem; }
+
+/* Segmented Toggle */
+.seg { display:inline-flex; border:1px solid var(--border); border-radius: 999px; overflow:hidden; background: #fff; box-shadow: var(--shadow); }
+.seg button { border:none; background:#fff; padding:8px 12px; font-weight:700; color:#334155; cursor:pointer; }
+.seg button + button { border-left:1px solid var(--border); }
+.seg button.active { background: #eff6ff; color:#1e3a8a; }
+
+/* Cards layout */
+.list { display: grid; gap: 12px; }
+.product { display: grid; grid-template-columns: 64px 1fr auto; gap: 12px; align-items: start; padding: 10px; border-radius: 12px; background: #ffffff; border: 1px solid var(--border); cursor: pointer; }
+.product:hover { border-color: var(--row-hover); }
+.product.selected { outline: 2px solid var(--accent); box-shadow: 0 0 0 3px rgba(20,184,166,.15); }
+
+.thumb { width: 64px; height: 64px; border-radius: 10px; overflow: hidden; position: relative; background: #f3f4f6; border:1px solid var(--border); }
+.thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
+.thumb .fallback { position:absolute; inset:0; display:grid; place-items:center; color:#64748b; font-weight:700; font-size: .8rem; background: #f8fafc; }
+
+.meta { display: grid; gap: 4px; }
+.meta .name { font-weight: 700; }
+.meta .desc { color: var(--muted); font-size: .92rem; }
+.badges { display:flex; gap:6px; flex-wrap: wrap; }
+
+.select { display:flex; align-items:center; gap:8px; }
+input[type="checkbox"] { width: 18px; height: 18px; }
+
+.divider { height:1px; background: var(--border); margin: 14px 0; }
+
+/* Inline attributes in cards */
+.attr-inline { grid-column: 1 / -1; margin-top: 10px; border-top: 1px dashed var(--border); padding-top: 10px; }
+.attr-inline .section-title { display:flex; align-items:center; gap:10px; margin: 6px 0 8px; color: var(--muted); }
+.chips { display:flex; flex-wrap:wrap; gap:8px; }
+.chip { background: var(--chip); border: 1px solid var(--border); border-radius: 999px; padding: 6px 9px; font-size: .86rem; display: inline-flex; gap: 8px; align-items: center; color: #1f2937; }
+.chip .k { color:#475569; }
+.chip .v { color:#111827; font-weight:600; }
+.chip.new { outline: 2px solid var(--accent); box-shadow: 0 0 0 3px rgba(20,184,166,.15); }
+.chip.new::after { content: 'NEW'; margin-left: 6px; font-size: .68rem; background: var(--accent); color: #07221f; padding: 3px 6px; border-radius: 999px; font-weight: 900; letter-spacing:.4px; }
+
+/* Table layout */
+.table-wrap { border: 1px solid var(--border); border-radius: 12px; overflow: hidden; box-shadow: var(--shadow); }
+table { width: 100%; border-collapse: collapse; background:#fff; }
+thead { background:#f8fafc; border-bottom:1px solid var(--border); }
+th, td { padding: 10px 12px; text-align: left; border-bottom: 1px solid var(--border); font-size: .93rem; vertical-align: top; }
+tbody tr:nth-child(odd) { background: var(--zebra); }
+tbody tr:hover { background: #f0f7ff; }
+td.select-cell { width: 60px; }
+td.thumb-cell { width: 60px; }
+.mini-thumb { width: 40px; height: 40px; border-radius: 8px; overflow: hidden; border:1px solid var(--border); background:#f3f4f6; display:grid; place-items:center; }
+.mini-thumb img { width: 100%; height: 100%; object-fit: cover; }
+.mini-thumb .fallback { color:#64748b; font-weight:700; font-size:.8rem; }
+.badge { display:inline-block; padding: 4px 8px; border-radius:999px; background:#eef2ff; color:#1e3a8a; font-size:.8rem; border:1px solid #e5e7eb; }
+
+/* Detail rows for table */
+.detail-row td { background:#ffffff; }
+.detail-content { padding: 10px 6px; border-left: 4px solid var(--accent); border-radius: 8px; background:#f8fafc; }
+.detail-content .section-title { display:flex; align-items:center; gap:10px; margin: 6px 0 8px; color: var(--muted); }
+.summary {
+    display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-top: 10px;
+}
+.summary .stat {  border:1px solid var(--border); padding: 12px; border-radius: 12px; text-align:center; }
+.stat .label { color: var(--muted); font-size:.85rem; }
+.stat .value { font-size: 1.25rem; font-weight: 800; }
+
+.empty { color: var(--muted); padding: 6px 0; }

+ 296 - 0
content_quality_tool_public/static/js/attr-extraction.js

@@ -0,0 +1,296 @@
+// --- Data ---
+const mediaUrl = "./../";
+const PRODUCT_BASE = [
+    { id: 1, item_id: 'SKU001', product_name: "Levi's Jeans", product_long_description: 'Classic blue denim jeans with straight fit.', product_short_description: 'Blue denim jeans.', product_type: 'Clothing', image_path: 'media/products/jeans.jpg', image: 'http://127.0.0.1:8000/media/products/jeans.png' },
+    { id: 2, item_id: 'SKU002', product_name: 'Adidas Running Shoes', product_long_description: 'Lightweight running shoes with breathable mesh and cushioned sole.', product_short_description: "Men's running shoes.", product_type: 'Footwear', image_path: 'media/products/shoes.png', image: 'http://127.0.0.1:8000/media/products/shoes.png' },
+    { id: 3, item_id: 'SKU003', product_name: 'Nike Sports T-Shirt', product_long_description: 'Moisture-wicking sports tee ideal for training and outdoor activities.', product_short_description: 'Performance t-shirt.', product_type: 'Clothing', image_path: 'media/products/tshirt.png', image: 'http://127.0.0.1:8000/media/products/tshirt.png' },
+    { id: 4, item_id: 'SKU004', product_name: 'Puma Hoodie', product_long_description: 'Soft fleece hoodie with kangaroo pocket and adjustable drawstring.', product_short_description: 'Casual hoodie.', product_type: 'Clothing', image_path: 'media/products/hoodie.png', image: 'http://127.0.0.1:8000/media/products/hoodie.png' },
+    { id: 5, item_id: 'SKU005', product_name: 'Ray-Ban Sunglasses', product_long_description: 'Classic aviator sunglasses with UV protection lenses.', product_short_description: 'Aviator sunglasses.', product_type: 'Accessories', image_path: 'media/products/sunglasses.png', image: 'http://127.0.0.1:8000/media/products/sunglasses.png' }
+];
+
+const FAKE_API_RESPONSE = {
+    results: [
+    { product_id: 'SKU001', mandatory: { 'Clothing Neck Style': 'V-Neck', 'Clothing Top Style': 'Pullover', 'Condition': 'New', 'T-Shirt Type': 'Classic T-Shirt' }, additional: { 'Material': 'Turkish Pima Cotton', 'Size': 'Large', 'Color': 'Blue', 'Brand': 'Sierra', 'Fabric Type': 'Soft & Breathable', 'Fabric Composition': '95% Turkish Pima cotton', 'Care Instructions': 'Machine Washable', 'Sizes Available': 'S-XL' } },
+    { product_id: 'SKU002', mandatory: { 'Shoe Type': 'Running', 'Closure': 'Lace-Up', 'Condition': 'New', 'Gender': 'Men' }, additional: { 'Upper Material': 'Engineered Mesh', 'Midsole': 'EVA Foam', 'Outsole': 'Rubber', 'Color': 'Black/White', 'Brand': 'Adidas', 'Size': 'UK 9', 'Care Instructions': 'Surface Clean' } },
+    { product_id: 'SKU003', mandatory: { 'Clothing Neck Style': 'Crew Neck', 'Sleeve Length': 'Short Sleeve', 'Condition': 'New', 'T-Shirt Type': 'Performance' }, additional: { 'Material': 'Polyester Blend', 'Color': 'Red', 'Brand': 'Nike', 'Size': 'Medium', 'Fabric Technology': 'Dri-FIT', 'Care Instructions': 'Machine Wash Cold' } },
+    { product_id: 'SKU004', mandatory: { 'Clothing Top Style': 'Hoodie', 'Closure': 'Pullover', 'Condition': 'New', 'Fit': 'Relaxed' }, additional: { 'Material': 'Cotton Fleece', 'Color': 'Charcoal', 'Brand': 'Puma', 'Size': 'Large', 'Care Instructions': 'Machine Wash Warm' } },
+    { product_id: 'SKU005', mandatory: { 'Accessory Type': 'Sunglasses', 'Frame Style': 'Aviator', 'Condition': 'New', 'Lens Protection': 'UV 400' }, additional: { 'Frame Material': 'Metal', 'Lens Color': 'Green', 'Brand': 'Ray-Ban', 'Size': 'Standard', 'Case Included': 'Yes', 'Care Instructions': 'Clean with microfiber' } }
+    ],
+    total_products: 5,
+    successful: 5,
+    failed: 0
+};
+
+// --- State ---
+let selectedIds = new Set();
+const lastSeen = new Map(); // per-product memory for NEW highlighting (product_id -> maps)
+let layoutMode = 'cards'; // 'cards' | 'table'
+
+// --- Helpers ---
+const $ = (sel) => document.querySelector(sel);
+const el = (tag, cls) => { const e = document.createElement(tag); if (cls) e.className = cls; return e; }
+
+function updateSelectionInfo() {
+    const pill = $('#selectionInfo');
+    const total = PRODUCT_BASE.length;
+    const count = selectedIds.size;
+    pill.textContent = count === 0 ? 'No products selected' : `${count} of ${total} selected`;
+}
+
+function setChecked(id, checked) { if (checked) selectedIds.add(id); else selectedIds.delete(id); updateSelectionInfo(); }
+
+// --- Chips rendering ---
+function renderChips(container, obj, memoryMap) {
+    container.innerHTML = '';
+    let count = 0;
+    Object.entries(obj || {}).forEach(([k, v]) => {
+    const chip = el('span', 'chip');
+    const kEl = el('span', 'k'); kEl.textContent = k + ':';
+    const vEl = el('span', 'v'); vEl.textContent = ' ' + String(v);
+    chip.appendChild(kEl); chip.appendChild(vEl);
+    const was = memoryMap.get(k);
+    if (was === undefined || was !== v) chip.classList.add('new');
+    container.appendChild(chip);
+    memoryMap.set(k, v);
+    count++;
+    });
+    return count;
+}
+
+function findApiResultForProduct(p, index, api) { return api.results?.find(r => r.product_id === p.item_id) || api.results?.[index] || null; }
+
+// --- Cards layout ---
+function createProductCard(p) {
+    const row = el('div', 'product');
+    if (selectedIds.has(p.id)) row.classList.add('selected');
+
+    const left = el('div', 'thumb');
+    const img = new Image(); img.src = mediaUrl+p.image_path || p.image || '';
+     img.alt = `${p.product_name} image`;
+    console.log("img",img);
+    img.onerror = () => { img.remove(); const fb = el('div', 'fallback'); fb.textContent = (p.product_name || 'Product').split(' ').map(w => w[0]).slice(0,2).join('').toUpperCase(); left.appendChild(fb); };
+    left.appendChild(img);
+
+    const mid = el('div', 'meta');
+    const name = el('div', 'name'); name.textContent = p.product_name || '—';
+    const desc = el('div', 'desc'); desc.textContent = p.product_short_description || '';
+    const badges = el('div', 'badges');
+    const sku = el('span', 'pill'); sku.textContent = `SKU: ${p.item_id || '—'}`; badges.appendChild(sku);
+    const type = el('span', 'pill'); type.textContent = p.product_type || '—'; badges.appendChild(type);
+    const long = el('div', 'desc'); long.textContent = p.product_long_description || ''; long.style.marginTop = '4px';
+    mid.appendChild(name); mid.appendChild(desc); mid.appendChild(badges); mid.appendChild(long);
+
+    const right = el('label', 'select');
+    const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = selectedIds.has(p.id);
+    cb.addEventListener('change', () => { setChecked(p.id, cb.checked); row.classList.toggle('selected', cb.checked); });
+    const lbl = el('span'); lbl.textContent = 'Select';
+    right.appendChild(cb); right.appendChild(lbl);
+
+    // Inline attributes container (rendered on Submit)
+    const inline = el('div', 'attr-inline');
+    inline.dataset.pid = p.item_id; // use item_id for mapping
+
+    row.addEventListener('click', (e) => { if (e.target.tagName.toLowerCase() !== 'input') { cb.checked = !cb.checked; cb.dispatchEvent(new Event('change')); } });
+
+    row.appendChild(left); row.appendChild(mid); row.appendChild(right);
+    row.appendChild(inline);
+    return row;
+}
+
+function renderProductsCards() {
+    const cards = $('#cardsContainer');
+    cards.innerHTML = '';
+    PRODUCT_BASE.forEach(p => cards.appendChild(createProductCard(p)));
+}
+
+// --- Table layout ---
+function createMiniThumb(p) {
+    const mt = el('div', 'mini-thumb');
+    const img = new Image(); img.src = mediaUrl+p.image_path || p.image || ''; img.alt = `${p.product_name} image`;
+    console.log("img",img);
+    img.onerror = () => { img.remove(); const fb = el('div', 'fallback'); fb.textContent = (p.product_name || 'Product').split(' ').map(w => w[0]).slice(0,2).join('').toUpperCase(); mt.appendChild(fb); };
+    mt.appendChild(img);
+    return mt;
+}
+
+function renderProductsTable() {
+    const wrap = $('#tableContainer');
+    wrap.innerHTML = '';
+    const table = el('table');
+    const thead = el('thead'); const trh = el('tr');
+    const headers = ['Select', 'Image', 'Product', 'SKU', 'Type', 'Short Description'];
+    headers.forEach(h => { const th = el('th'); th.textContent = h; trh.appendChild(th); });
+    thead.appendChild(trh); table.appendChild(thead);
+    const tbody = el('tbody');
+
+    PRODUCT_BASE.forEach(p => {
+    const tr = el('tr'); tr.id = `row-${p.id}`;
+    // Select cell
+    const tdSel = el('td', 'select-cell'); const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = selectedIds.has(p.id);
+    cb.addEventListener('change', () => { setChecked(p.id, cb.checked); tr.classList.toggle('selected', cb.checked); }); tdSel.appendChild(cb); tr.appendChild(tdSel);
+    // Image
+    const tdImg = el('td', 'thumb-cell'); tdImg.appendChild(createMiniThumb(p)); tr.appendChild(tdImg);
+    // Product name
+    const tdName = el('td'); tdName.textContent = p.product_name || '—'; tr.appendChild(tdName);
+    // SKU
+    const tdSku = el('td'); tdSku.textContent = p.item_id || '—'; tr.appendChild(tdSku);
+    // Type
+    const tdType = el('td'); const b = el('span', 'badge'); b.textContent = p.product_type || '—'; tdType.appendChild(b); tr.appendChild(tdType);
+    // Short description
+    const tdDesc = el('td'); tdDesc.textContent = p.product_short_description || ''; tr.appendChild(tdDesc);
+
+    tr.addEventListener('click', (e) => { if (e.target.tagName.toLowerCase() !== 'input') { cb.checked = !cb.checked; cb.dispatchEvent(new Event('change')); } });
+    tbody.appendChild(tr);
+    });
+
+    table.appendChild(tbody);
+    wrap.appendChild(table);
+}
+
+// --- Inline attributes rendering ---
+function renderInlineForCards() {
+    const api = FAKE_API_RESPONSE;
+    // Clear all inline sections first
+    document.querySelectorAll('.attr-inline').forEach(div => div.innerHTML = '');
+
+    PRODUCT_BASE.forEach((p, idx) => {
+    const inline = document.querySelector(`.attr-inline[data-pid="${p.item_id}"]`);
+    if (!inline) return;
+    if (!selectedIds.has(p.id)) return; // only show for selected
+    const res = findApiResultForProduct(p, idx, api);
+    const pid = p.item_id;
+    if (!lastSeen.has(pid)) lastSeen.set(pid, { mandatory: new Map(), additional: new Map() });
+    const mem = lastSeen.get(pid);
+
+    // Build sections
+    const manTitle = el('div', 'section-title'); manTitle.innerHTML = '<strong>Mandatory</strong>';
+    const manChips = el('div', 'chips');
+    const addTitle = el('div', 'section-title'); addTitle.innerHTML = '<strong>Additional</strong>';
+    const addChips = el('div', 'chips');
+
+    const mandCount = renderChips(manChips, res?.mandatory || {}, mem.mandatory);
+    const addCount = renderChips(addChips, res?.additional || {}, mem.additional);
+
+    const counts = el('div'); counts.style.display = 'flex'; counts.style.gap = '8px'; counts.style.margin = '8px 0 0';
+    const c1 = el('span', 'pill'); c1.textContent = `Mandatory: ${mandCount}`;
+    const c2 = el('span', 'pill'); c2.textContent = `Additional: ${addCount}`;
+    counts.appendChild(c1); counts.appendChild(c2);
+
+    inline.appendChild(manTitle); inline.appendChild(manChips);
+    inline.appendChild(addTitle); inline.appendChild(addChips);
+    inline.appendChild(counts);
+    });
+
+    // Update summary
+    $('#statTotal').textContent = api.total_products ?? 0;
+    $('#statOk').textContent = api.successful ?? 0;
+    $('#statKo').textContent = api.failed ?? 0;
+}
+
+function renderInlineForTable() {
+    const api = FAKE_API_RESPONSE;
+    const table = $('#tableContainer');
+    if (!table) return;
+    // Remove existing detail rows
+    table.querySelectorAll('tr.detail-row').forEach(r => r.remove());
+
+    PRODUCT_BASE.forEach((p, idx) => {
+    if (!selectedIds.has(p.id)) return;
+    const res = findApiResultForProduct(p, idx, api);
+    const pid = p.item_id;
+    if (!lastSeen.has(pid)) lastSeen.set(pid, { mandatory: new Map(), additional: new Map() });
+    const mem = lastSeen.get(pid);
+
+    const tbody = table.querySelector('tbody');
+    const baseRow = tbody.querySelector(`#row-${p.id}`);
+    if (!baseRow) return;
+
+    const detail = el('tr', 'detail-row');
+    const td = el('td'); td.colSpan = 6; // number of columns
+    const content = el('div', 'detail-content');
+
+    const manTitle = el('div', 'section-title'); manTitle.innerHTML = '<strong>Mandatory</strong>';
+    const manChips = el('div', 'chips');
+    const addTitle = el('div', 'section-title'); addTitle.innerHTML = '<strong>Additional</strong>';
+    const addChips = el('div', 'chips');
+
+    const mandCount = renderChips(manChips, res?.mandatory || {}, mem.mandatory);
+    const addCount = renderChips(addChips, res?.additional || {}, mem.additional);
+
+    const counts = el('div'); counts.style.display = 'flex'; counts.style.gap = '8px'; counts.style.margin = '8px 0 0';
+    const c1 = el('span', 'pill'); c1.textContent = `Mandatory: ${mandCount}`;
+    const c2 = el('span', 'pill'); c2.textContent = `Additional: ${addCount}`;
+    counts.appendChild(c1); counts.appendChild(c2);
+
+    content.appendChild(manTitle); content.appendChild(manChips);
+    content.appendChild(addTitle); content.appendChild(addChips);
+    content.appendChild(counts);
+    td.appendChild(content); detail.appendChild(td);
+
+    // insert after base row
+    baseRow.insertAdjacentElement('afterend', detail);
+    });
+
+    // Update summary
+    $('#statTotal').textContent = api.total_products ?? 0;
+    $('#statOk').textContent = api.successful ?? 0;
+    $('#statKo').textContent = api.failed ?? 0;
+}
+
+function renderInlineAttributes() {
+    if (layoutMode === 'cards') renderInlineForCards(); else renderInlineForTable();
+}
+
+// --- Main rendering ---
+function renderProducts() {
+    if (layoutMode === 'cards') {
+    $('#cardsContainer').style.display = '';
+    $('#tableContainer').style.display = 'none';
+    renderProductsCards();
+    } else {
+    $('#cardsContainer').style.display = 'none';
+    $('#tableContainer').style.display = '';
+    renderProductsTable();
+    }
+    updateSelectionInfo();
+    // If there is a selection, re-render inline attributes (persist across toggle)
+    if (selectedIds.size > 0) renderInlineAttributes();
+}
+
+// --- Submit & Reset ---
+function submitAttributes() {
+    if (selectedIds.size === 0) { alert('Please select at least one product.'); return; }
+    renderInlineAttributes();
+}
+
+function resetAll() {
+    selectedIds.clear();
+    lastSeen.clear();
+    renderProducts();
+    // Clear summary
+    document.getElementById('statTotal').textContent = '0';
+    document.getElementById('statOk').textContent = '0';
+    document.getElementById('statKo').textContent = '0';
+}
+
+function setLayout(mode) {
+    layoutMode = mode;
+    const btnCards = document.getElementById('btnCards');
+    const btnTable = document.getElementById('btnTable');
+    if (mode === 'cards') { btnCards.classList.add('active'); btnCards.setAttribute('aria-selected', 'true'); btnTable.classList.remove('active'); btnTable.setAttribute('aria-selected', 'false'); }
+    else { btnTable.classList.add('active'); btnTable.setAttribute('aria-selected', 'true'); btnCards.classList.remove('active'); btnCards.setAttribute('aria-selected', 'false'); }
+    renderProducts();
+}
+
+// --- Wire up ---
+document.addEventListener('DOMContentLoaded', () => {
+    renderProducts();
+    document.getElementById('btnSubmit').addEventListener('click', submitAttributes);
+    document.getElementById('btnReset').addEventListener('click', resetAll);
+    document.getElementById('btnSelectAll').addEventListener('click', () => {
+    if (selectedIds.size === PRODUCT_BASE.length) { selectedIds.clear(); } else { selectedIds = new Set(PRODUCT_BASE.map(p => p.id)); }
+    renderProducts();
+    });
+    document.getElementById('btnCards').addEventListener('click', () => setLayout('cards'));
+    document.getElementById('btnTable').addEventListener('click', () => setLayout('table'));
+});

+ 64 - 21
content_quality_tool_public/templates/attr-extraction.html

@@ -16,6 +16,8 @@
   <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
   <link rel="stylesheet" href="{% static './css/select2-bootstrap4.min.css' %}">
   <link rel="stylesheet" href="{% static 'css/custom.css' %}">
+  <link rel="stylesheet" href="{% static 'css/attr-extraction.css' %}">
+
 
 </head>
 <body class="layout-fixed sidebar-expand-lg sidebar-mini app-loaded sidebar-collapse">
@@ -42,25 +44,71 @@
       </div>
       <div class="app-content-header"> <!--begin::Container-->
           <div class="container-fluid "> <!--begin::Row-->
-            
+            <!-- attr -->
+                  <div class="container">
+                    <header class="hero">
+                    <div class="title">
+                        <div class="title-logo" aria-hidden="true">
+                        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                            <path d="M5 12c0-3.866 3.134-7 7-7 2.7 0 5.06 1.486 6.3 3.69L12 12l-6.3 3.69C3.74 13.486 5 10.7 5 12z" fill="white" fill-opacity=".95"/>
+                            <path d="M12 12l6.3-3.69C20.26 10.514 19 13.3 19 12c0 3.866-3.134 7-7 7-2.7 0-5.06-1.486-6.3-3.69L12 12z" fill="#9FECE2"/>
+                        </svg>
+                        </div>
+                        <div>
+                        <h1>Product Inventory</h1>
+                        </div>
+                    </div>
+                    <div class="actions">
+                        <button id="btnSubmit" class="btn btn-success">Submit</button>
+                        <button id="btnReset" class="btn btn-secondary">Reset</button>
+                        <button id="btnSelectAll" class="btn btn-ghost">Select all</button>
+                    </div>
+                    </header>
+
+                    <div class="grid">
+                    <section class="card">
+                        <div class="card-header">
+                        <h2>API Summary</h2>
+                        </div>
+                        <div class="card-body">
+                        <div class="summary">
+                            <div class="stat"><div class="label">Total products</div><div class="value" id="statTotal">0</div></div>
+                            <div class="stat"><div class="label">Successful</div><div class="value" id="statOk">0</div></div>
+                            <div class="stat"><div class="label">Failed</div><div class="value" id="statKo">0</div></div>
+                        </div>
+                        </div>
+                    </section>    
+                    <section class="card">
+                        <div class="card-header">
+                        <h2>Products</h2>
+                        <div class="toolbar">
+                            <span class="pill" id="selectionInfo">No products selected</span>
+                            <div class="seg" role="tablist" aria-label="Layout toggle">
+                            <button id="btnCards" class="active" role="tab" aria-selected="true">Cards</button>
+                            <button id="btnTable" role="tab" aria-selected="false">Table</button>
+                            </div>
+                        </div>
+                        </div>
+                        <div class="card-body">
+                        <!-- Cards layout container -->
+                        <div id="cardsContainer" class="list" aria-live="polite"></div>
+                        <!-- Table layout container -->
+                        <div id="tableContainer" class="table-wrap" style="display:none" aria-live="polite"></div>
+                        </div>
+                    </section>
+
+                    
+                    </div>
+
+                    <!-- <footer> -->
+                    <!-- <small>Tip: Select products and click <strong>Submit</strong>—attributes render inline under each selected product. Toggle <strong>Cards</strong>/<strong>Table</strong> views.</small> -->
+                    <!-- </footer> -->
+                </div>
+            <!-- attr -->
           </div>
       </div>
     </main>
 
-    <!-- Video Modal -->
-    <div class="modal fade" id="videoModal" tabindex="-1" aria-hidden="true">
-      <div class="modal-dialog modal-dialog-centered modal-lg">
-        <div class="modal-content">
-          <div class="modal-header">
-            <h5 class="modal-title">🎉 Your Video is Ready!</h5>
-            <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
-          </div>
-          <div class="modal-body">
-            <video id="outputVideo" class="w-100" controls></video>
-          </div>
-        </div>
-      </div>
-    </div>
 
     {% include 'footer.html' %}
   </div>
@@ -100,13 +148,8 @@
                 });
             }
         });
-        $(document).ready(function () {
-            $('.select2').select2({
-                theme: 'bootstrap4',
-                placeholder: 'Select Competitors'
-            });
-        });
     </script> 
+    <script src="{% static 'js/attr-extraction.js' %}"></script>
     
 </body>
 </html>

+ 1 - 1
content_quality_tool_public/templates/get-data.html

@@ -172,7 +172,7 @@
                         </div>
                         <div class="col-sm-6">
                             <ol class="breadcrumb float-sm-end">
-                                <li class="breadcrumb-item"><a href="{% url 'file-upload' %}">Home</a></li>
+                                <li class="breadcrumb-item"><a href="{% url 'content-scorecard' %}">Home</a></li>
                                 <li class="breadcrumb-item active" aria-current="page">
                                     📑 Scorecard
                                 </li>

+ 2 - 3
content_quality_tool_public/templates/header.html

@@ -20,9 +20,8 @@
             <li class="nav-item"> <a class="nav-link" data-lte-toggle="sidebar" href="#" role="button"> <i
                         class="bi bi-list"></i> </a> </li>
             <!-- <li class="nav-item d-none d-md-block"> <a  href="{% url 'file-upload' %}" class="nav-link">File Upload</a> 
-            </li>
-            <li class="nav-item d-none d-md-block"> <a  href="{% url 'tool-check' %}" class="nav-link">Home</a> 
-            </li> -->
+            </li>-->
+            <!-- abcd -->
         </ul> <!--end::Start Navbar Links--> <!--begin::End Navbar Links-->
         <ul class="navbar-nav ms-auto"> <!--begin::Navbar Search-->
             <li class="nav-item"> <a class="nav-link" href="#" data-lte-toggle="fullscreen"> <i

+ 2 - 2
content_quality_tool_public/templates/index.html

@@ -64,7 +64,7 @@
                         <div class="col-sm-6">
                             <ol class="breadcrumb float-sm-end">
                                 <li class="breadcrumb-item"><a href="{% url 'file-upload' %}">Home</a></li>
-                                <li class="breadcrumb-item active" aria-current="page"><a href="{% url 'file-upload' %}"></a>
+                                <li class="breadcrumb-item active" aria-current="page"><a href="{% url 'content-scorecard' %}"></a>
                                    📂 File Upload</a>
                                 </li>
                             </ol>
@@ -236,7 +236,7 @@
                 // Remove message after 5 seconds
                 setTimeout(() => {
                     responseDiv.innerHTML = '';
-                    window.location.href = "/tool-check";
+                    window.location.href = "/content-scorecard";
                 }, 3000);
             });
         });

+ 4 - 0
content_quality_tool_public/templates/sidebar.html

@@ -15,6 +15,10 @@
                             class="nav-icon bi bi-upload"></i>
                         <p>Upload</p>
                     </a> </li>
+                <!-- <li class="nav-item"> <a href="{% url 'content-scorecard' %}" class="nav-link {% if request.path == '/content-scorecard/' %}active{% endif %}"> <i
+                            class="nav-icon bi bi-house"></i>
+                        <p>Home</p>
+                    </a> </li>   -->
                 <li class="nav-item"> <a href="{% url 'generate-video' %}" class="nav-link {% if request.path == '/video/generate-video/' %}active{% endif %}"> <i
                             class="nav-icon bi bi-cpu"></i>
                         <p>Generate Video</p>

+ 1 - 1
content_quality_tool_public/urls.py

@@ -9,7 +9,7 @@ urlpatterns = [
     path('', views.login_view, name='login'),
     path('login/', views.logout_view, name='logout'),
     path('home/', views.upload, name='file-upload'),
-    path('tool-check/', views.getData, name='tool-check'),
+    path('content-scorecard/', views.getData, name='content-scorecard'),
     path('attribute-extraction/', views.getAttributeExtraction, name='attribute-extraction'),
 ]