Harshit Pathak 3 miesięcy temu
rodzic
commit
ba7fe3635f

+ 125 - 1
content_quality_tool_public/static/css/attr-extraction.css

@@ -68,7 +68,7 @@ button:hover { transform: translateY(-1px); border-color: #cbd5e1; }
 
 /* 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 { display: grid; grid-template-columns: 64px 1fr 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); }
 
@@ -216,3 +216,127 @@ td.thumb-cell { width: 60px; }
   font-size: 0.9em;
   color: #666;
 }
+/* 
+/* General layout */
+.attribute-selectors {
+    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+    max-width: none;
+    margin: 5px auto;
+    padding: 15px;
+    background-color: #fff;
+    border: 1px solid #ddd;
+    border-radius: 10px;
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
+    width: max-content;
+}
+
+/* Section titles */
+.mandatory-title,
+.optional-title {
+    font-size: 1.2em;
+    font-weight: 600;
+    color: #333;
+    margin-bottom: 10px;
+    padding-left: 5px;
+    border-left: 4px solid #007BFF;
+}
+
+/* Attribute group block */
+.attribute-chip-group {
+    margin-bottom: 25px;
+}
+
+/* Attribute headers */
+.attribute-header {
+    font-size: 1em;
+    font-weight: 500;
+    color: #555;
+    margin-bottom: 10px;
+}
+
+/* Chips container layout */
+.chips-container {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 10px;
+}
+
+/* Individual chip */
+.attribute-chip {
+    display: inline-flex;
+    align-items: center;
+    background-color: #f0f4f8;
+    border: 1px solid #ccc;
+    border-radius: 20px;
+    padding: 8px 14px;
+    cursor: pointer;
+    transition: background-color 0.2s, box-shadow 0.2s;
+    user-select: none;
+    font-size: 0.95em;
+}
+
+/* Hide the native checkbox */
+.attribute-chip input[type="checkbox"] {
+    display: none;
+}
+
+/* Style span inside chip */
+.attribute-chip span {
+    color: #333;
+}
+
+/* Highlight chip when selected */
+.attribute-chip input[type="checkbox"]:checked + span {
+    font-weight: 600;
+    color: #007BFF;
+}
+
+/* Add visual feedback for selected chips */
+.attribute-chip input[type="checkbox"]:checked + span::before {
+    content: "✔ ";
+    color: #007BFF;
+    margin-right: 4px;
+}
+
+/* Hover effect */
+.attribute-chip:hover {
+    background-color: #e6f0ff;
+    border-color: #007BFF;
+    box-shadow: 0 2px 6px rgba(0, 123, 255, 0.2);
+}
+
+/* Responsive */
+@media (max-width: 600px) {
+    .chips-container {
+        flex-direction: column;
+        gap: 8px;
+    }
+
+    .attribute-chip {
+        width: 100%;
+        justify-content: center;
+    }
+}
+
+
+/* MOBILE STYLING */
+@media (max-width: 600px) {
+    .product {
+        grid-template-columns: 48px auto;
+        grid-template-rows: auto auto;
+        gap: 8px;
+        align-items: center;
+        padding: 12px;
+    }
+
+    .product > :nth-child(2),
+    .product > :nth-child(3) {
+        grid-column: 2 / span 1;
+    }
+
+    .product > :nth-child(4) {
+        grid-column: 1 / -1;
+        justify-self: end;
+        margin-top: 8px;
+    }
+}

+ 14 - 1
content_quality_tool_public/static/css/custom.css

@@ -118,4 +118,17 @@ body {
     justify-content: center;
     z-index: 1050;
     /* above most elements */
-}
+}
+
+.selectRight{
+    padding-left: 50px;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+}
+
+
+.attributeName{
+    font-weight: 400;
+    color: black;
+}

+ 1035 - 0
content_quality_tool_public/static/js/attr-extraction-new.js

@@ -0,0 +1,1035 @@
+jQuery.noConflict(); // Release $ to other libraries
+console.log(typeof jQuery);
+// $ = jQuery;
+
+// --- Config ---
+const UPLOAD_API_URL = '/attr/products/upload-excel/'; // TODO: set to your upload endpoint
+const ACCEPT_TYPES = '*'; // e.g., 'image/*,.csv,.xlsx'
+
+var 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' }
+];
+// --- Data ---
+const mediaUrl = "./../";
+
+document.addEventListener('DOMContentLoaded', () => {
+    jQuery('#full-page-loader').show();  
+    fetch('/attr/products', {
+        method: 'GET', // or 'POST' if your API expects POST
+        headers: {
+            'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || ''
+        }
+    })
+    .then(response => response.json())
+    .then(data => {
+        console.log("data",data);
+        // --- Wire up ---
+        PRODUCT_BASE = data;
+        // PRODUCT_BASE = PRODUCT_BASE.map((d)=>{return {...d,mandatoryAttributes:["color","size"]}});
+        // console.log("PRODUCT_BASE",PRODUCT_BASE);
+        if(PRODUCT_BASE.length > 0){
+            $('#paginationBar').style.display = 'block';
+        }
+        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();
+        // });
+
+        // Replace your existing Select All listener with this:
+          document.getElementById('btnSelectAll').addEventListener('click', () => {
+            // Use the container for the active layout
+            const container = (layoutMode === 'cards')
+              ? document.getElementById('cardsContainer')
+              : document.getElementById('tableContainer');
+
+            // Collect all visible checkboxes
+            const boxes = Array.from(container.querySelectorAll('input[type="checkbox"]'));
+
+            // If every visible checkbox is already checked, we'll deselect; otherwise select all
+            const allChecked = boxes.length > 0 && boxes.every(cb => cb.checked);
+
+            boxes.forEach(cb => {
+              const target = !allChecked; // true to select, false to deselect
+              if (cb.checked !== target) {
+                cb.checked = target;
+                // Trigger your existing "change" handler so selectedIds & row .selected class update
+                cb.dispatchEvent(new Event('change', { bubbles: true }));
+              }
+            });
+
+            // Update the selection pill text (doesn't re-render the list)
+            updateSelectionInfo();
+          });
+        document.getElementById('btnCards').addEventListener('click', () => setLayout('cards'));
+        document.getElementById('btnTable').addEventListener('click', () => setLayout('table'));
+        jQuery('#full-page-loader').hide();
+        // if (data.success) {
+        // }
+    });
+});
+
+var 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();
+// NEW: Array of objects { item_id: string, mandatory_attrs: { [attribute_name]: string[] } }
+let selectedProductsWithAttributes = [];
+let selectedAttributes = new Array();
+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;
+    const count = selectedProductsWithAttributes.length;
+    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(); }
+// function setCheckedAttributes(id,attribute, checked) { if (checked) selectedAttributes.add({id: [attribute]}); else selectedIds.delete({id:[attribute]}); 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');
+    // Check selection using the new helper
+    if (isProductSelected(p.item_id)) row.classList.add('selected');
+    // if (selectedIds.has(p.item_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); };
+    img.onerror = () => { img.src = mediaUrl+"media/images/no-product.png" };
+    left.appendChild(img);
+
+    const mid = el('div', 'meta');
+    const name = el('div', 'name'); name.textContent = p.product_name || '—';
+    const desc = el('div', 'desc'); desc.innerHTML = 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.innerHTML = p.product_long_description || ''; long.style.marginTop = '4px';
+    mid.appendChild(name); mid.appendChild(desc); mid.appendChild(badges); mid.appendChild(long);
+
+
+    // const rightAttribute = el('label', 'select');
+
+    // // Main checkbox: "Select"
+    // const cb_attribute = document.createElement('input');
+    // cb_attribute.type = 'checkbox';
+    // cb_attribute.checked = selectedIds.has(p.item_id);
+    // cb_attribute.addEventListener('change', () => {
+    //   setChecked(p.item_id, cb_attribute.checked);
+    //   row.classList.toggle('selected', cb_attribute.checked);
+    // });
+    // const lbl_attribute = el('span');
+    // lbl_attribute.textContent = 'Select';
+
+    // rightAttribute.appendChild(cb_attribute);
+    // rightAttribute.appendChild(lbl_attribute);
+
+    // // --- New checkbox for mandatory attributes ---
+    // if (p.mandatoryAttributes && p.mandatoryAttributes.length > 0) {
+    //   const attrLabel = el('label', 'select-attr');
+    //   const attrCb = document.createElement('input');
+    //   attrCb.type = 'checkbox';
+    //   attrCb.checked = false;  // Not selected by default
+
+    //   attrCb.addEventListener('change', () => {
+    //     if (attrCb.checked) {
+    //       // Select the main checkbox
+    //       cb_attribute.checked = true;
+    //       setChecked(p.item_id, true);
+    //       row.classList.add('selected');
+    //     } else {
+    //       // Optionally uncheck main if attrCb is unchecked
+    //       // (optional logic)
+    //     }
+    //   });
+
+    //   const attrSpan = el('span');
+    //   attrSpan.textContent = `Select mandatory (${p.mandatoryAttributes.join(', ')})`;
+
+    //   attrLabel.appendChild(attrCb);
+    //   attrLabel.appendChild(attrSpan);
+
+    //   rightAttribute.appendChild(attrLabel);
+    // }
+     
+    // const attri = el('p',"pSelectRight");
+    // attri.innerHTML = "Select Attribute : ";
+    // var secondRight = el('label', 'selectRight');
+    // if (p.mandatoryAttributes && p.mandatoryAttributes.length > 0) {
+      
+    //   p.mandatoryAttributes.forEach((data,index)=>{  
+             
+    //     const cbSeond = document.createElement('input'); 
+    //     cbSeond.type = 'checkbox'; 
+    //     cbSeond.checked = selectedIds.has(p.item_id);
+    //     cbSeond.addEventListener('change', () => { setChecked(p.item_id, cbSeond.checked); 
+    //     row.classList.toggle('selected', cbSeond.checked); });
+        
+    //     const lblsecond = el('span');
+    //     lblsecond.className = "attributeName"; 
+    //     lblsecond.textContent = data ;
+    //     secondRight.appendChild(cbSeond); secondRight.appendChild(lblsecond);
+    //   })
+
+    // }
+
+    
+    // const right = el('label', 'select');
+    // const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = selectedIds.has(p.item_id);
+    // cb.addEventListener('change', () => { setChecked(p.item_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')); } });
+
+    // Helper function to create the chip UI for attributes
+    function createAttributeChips(p, attr, initialSelected, isMandatory, updateCallback) {
+        const wrapper = el('div', 'attribute-chip-group');
+        wrapper.dataset.attrName = attr.attribute_name;
+        wrapper.innerHTML = `<p class="attribute-header">${attr.attribute_name} (${isMandatory ? 'Mandatory' : 'Optional'}):</p>`;
+
+        const chipContainer = el('div', 'chips-container');
+
+        attr.possible_values.forEach(value => {
+            const chip = el('label', 'attribute-chip');
+            
+            // Checkbox input is hidden, but drives the selection state
+            const checkbox = document.createElement('input');
+            checkbox.type = 'checkbox';
+            checkbox.value = value;
+            checkbox.name = `${p.item_id}-${attr.attribute_name}`;
+            
+            // Set initial state
+            checkbox.checked = initialSelected.includes(value); 
+            
+            // The visual part of the chip
+            const span = el('span');
+            span.textContent = value;
+            
+            chip.appendChild(checkbox);
+            chip.appendChild(span);
+            chipContainer.appendChild(chip);
+        });
+        
+        // Use event delegation on the container for performance
+        chipContainer.addEventListener('change', updateCallback);
+
+        wrapper.appendChild(chipContainer);
+        return wrapper;
+    }
+
+    // --- Main Select Checkbox (Product Selection) ---
+    const right = el('label', 'select');
+    const cb = document.createElement('input'); cb.type = 'checkbox';
+    cb.checked = isProductSelected(p.item_id);
+    const lbl = el('span'); lbl.textContent = 'Select Product';
+    right.appendChild(cb); right.appendChild(lbl);
+
+    // --- Dynamic Attribute Selects ---
+    const attrContainer = el('div', 'attribute-selectors');
+
+    // Find all mandatory and non-mandatory attributes for this product
+    const mandatoryAttributes = p.product_type_details?.filter(a => a.is_mandatory === 'Yes') || [];
+    const optionalAttributes = p.product_type_details?.filter(a => a.is_mandatory !== 'Yes') || [];
+
+    // Helper to update the main state object with all current selections
+    // const updateProductState = () => {
+    //     const isSelected = cb.checked;
+    //     const currentSelections = {};
+
+    //     // Only process attributes if the main product is selected
+    //     if (isSelected) {
+    //         // Get selections from the dynamic selects
+    //         attrContainer.querySelectorAll('select').forEach(select => {
+    //             const attrName = select.dataset.attrName;
+    //             const selectedOptions = Array.from(select.options)
+    //                 .filter(option => option.selected)
+    //                 .map(option => option.value);
+
+    //             if (selectedOptions.length > 0) {
+    //                 currentSelections[attrName] = selectedOptions;
+    //             }
+    //         });
+    //     }
+        
+    //     // Use toggleProductSelection to update the state
+    //     toggleProductSelection(p.item_id, isSelected, currentSelections);
+    //     row.classList.toggle('selected', isSelected);
+    // };
+
+    // // Attach listener to main checkbox
+    // cb.addEventListener('change', () => {
+    //     // Toggle the visibility/enabled state of the attribute selects
+    //     attrContainer.classList.toggle('disabled', !cb.checked);
+    //     updateProductState();
+    // });
+    
+    // // --- Render Mandatory Attributes ---
+    // if (mandatoryAttributes.length > 0) {
+    //     const manTitle = el('p', "pSelectRight mandatory-title");
+    //     manTitle.innerHTML = "Mandatory Attributes:";
+    //     attrContainer.appendChild(manTitle);
+
+    //     mandatoryAttributes.forEach(attr => {
+    //         const label = el('label', 'attribute-label');
+    //         label.textContent = `${attr.attribute_name} (${attr.is_mandatory === 'Yes' ? 'Mandatory' : 'Optional'}):`;
+            
+    //         const select = el('select', 'attribute-select');
+    //         select.multiple = true;
+    //         select.dataset.attrName = attr.attribute_name;
+
+    //         // Get previously selected values or default to all
+    //         const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+
+    //         attr.possible_values.forEach(value => {
+    //             const option = document.createElement('option');
+    //             option.value = value;
+    //             option.textContent = value;
+    //             // Check if this option should be pre-selected
+    //             option.selected = initialSelected.includes(value); 
+    //             select.appendChild(option);
+    //         });
+
+    //         select.addEventListener('change', updateProductState); // Update state when a selection changes
+            
+    //         label.appendChild(select);
+    //         attrContainer.appendChild(label);
+    //     });
+    // }
+
+    // // --- Render Optional Attributes (Same structure, different title) ---
+    // if (optionalAttributes.length > 0) {
+    //     const optTitle = el('p', "pSelectRight optional-title");
+    //     optTitle.innerHTML = "Optional Attributes:";
+    //     attrContainer.appendChild(optTitle);
+
+    //     optionalAttributes.forEach(attr => {
+    //         const label = el('label', 'attribute-label');
+    //         label.textContent = `${attr.attribute_name} (Optional):`;
+            
+    //         const select = el('select', 'attribute-select');
+    //         select.multiple = true;
+    //         select.dataset.attrName = attr.attribute_name;
+
+    //         // Get previously selected values or default to all
+    //         const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+
+    //         attr.possible_values.forEach(value => {
+    //             const option = document.createElement('option');
+    //             option.value = value;
+    //             option.textContent = value;
+    //             // Check if this option should be pre-selected
+    //             option.selected = initialSelected.includes(value);
+    //             select.appendChild(option);
+    //         });
+
+    //         select.addEventListener('change', updateProductState);
+            
+    //         label.appendChild(select);
+    //         attrContainer.appendChild(label);
+    //     });
+    // }
+
+    // Helper to update the main state object with all current selections
+    const updateProductState = () => {
+        const isSelected = cb.checked;
+        const currentSelections = {};
+
+        if (isSelected) {
+            // Iterate over all attribute groups (Mandatory and Optional)
+            attrContainer.querySelectorAll('.attribute-chip-group').forEach(group => {
+                const attrName = group.dataset.attrName;
+                
+                // Collect selected chip values
+                const selectedOptions = Array.from(group.querySelectorAll('input[type="checkbox"]:checked'))
+                    .map(checkbox => checkbox.value);
+
+                if (selectedOptions.length > 0) {
+                    currentSelections[attrName] = selectedOptions;
+                }
+            });
+        }
+        
+        toggleProductSelection(p.item_id, isSelected, currentSelections);
+        row.classList.toggle('selected', isSelected);
+    };
+
+    // Attach listener to main checkbox
+    cb.addEventListener('change', () => {
+        attrContainer.classList.toggle('disabled', !cb.checked);
+        updateProductState();
+    });
+    
+    // --- Render Mandatory Attributes ---
+    if (mandatoryAttributes.length > 0) {
+        const manTitle = el('p', "pSelectRight mandatory-title");
+        manTitle.innerHTML = "Mandatory Attributes:";
+        attrContainer.appendChild(manTitle);
+        mandatoryAttributes.forEach(attr => {
+            const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+            const chipGroup = createAttributeChips(p, attr, true, updateProductState); // Pass true for isMandatory
+            attrContainer.appendChild(chipGroup);
+        });
+        // mandatoryAttributes.forEach(attr => {
+        //     const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+        //     const chipGroup = createAttributeChips(p, attr, initialSelected, true, updateProductState);
+        //     attrContainer.appendChild(chipGroup);
+        // });
+    }
+
+    // --- Render Optional Attributes ---
+    if (optionalAttributes.length > 0) {
+    //   const br = el('br');
+        const optTitle = el('p', "pSelectRight optional-title");
+        optTitle.innerHTML = "Optional Attributes:";
+        // attrContainer.appendChild(br);
+        attrContainer.appendChild(optTitle);
+        optionalAttributes.forEach((attr, index) => { // <-- Use index here
+            const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+            const chipGroup = createAttributeChips(p, attr, false, updateProductState);
+            
+            // Add a class to the first optional group for CSS targeting
+            if (index === 0) {
+                chipGroup.classList.add('first-optional-group'); 
+            }
+
+            attrContainer.appendChild(chipGroup);
+        });
+        // optionalAttributes.forEach(attr => {
+        //     const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+        //     const chipGroup = createAttributeChips(p, attr, false, updateProductState); // Pass false for isMandatory
+        //     attrContainer.appendChild(chipGroup);
+        // });
+        // optionalAttributes.forEach(attr => {
+        //     const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+        //     const chipGroup = createAttributeChips(p, attr, initialSelected, false, updateProductState);
+        //     attrContainer.appendChild(chipGroup);
+        // });
+    }
+
+    // Initialize attribute selectors' enabled state and state data
+    attrContainer.classList.toggle('disabled', !cb.checked);
+    // Initial state setup if the product was already selected (e.g., after a re-render)
+    if (cb.checked) {
+        // This is important to set the initial state correctly on load
+        // We defer this until all selects are mounted, or ensure the initial state is correct.
+        // For simplicity, we assume the data from PRODUCT_BASE already includes selected attributes if a selection exists
+        // (which it won't in this case, so they default to all/empty)
+    }
+
+    const inline = el('div', 'attr-inline');
+    inline.dataset.pid = p.item_id; // use item_id for mapping
+
+    // row.addEventListener('click', (e) => {
+    //     // Only toggle main checkbox if an attribute select or its option wasn't clicked
+    //     if (e.target.tagName.toLowerCase() !== 'input' && e.target.tagName.toLowerCase() !== 'select' && e.target.tagName.toLowerCase() !== 'option') {
+    //         cb.checked = !cb.checked;
+    //         cb.dispatchEvent(new Event('change'));
+    //     }
+    // });
+
+    row.appendChild(left); row.appendChild(mid);
+    
+    row.appendChild(right);
+    // if (p.mandatoryAttributes && p.mandatoryAttributes.length > 0) { 
+      // const hr = el('hr');
+      // row.appendChild(hr);
+      // row.appendChild(attri);
+      // row.appendChild(secondRight);
+    // }
+    row.appendChild(attrContainer); // Append the new attribute selectors container
+    row.appendChild(inline);
+    
+    return row;
+}
+
+// function renderProductsCards() {
+//     const cards = $('#cardsContainer');
+//     cards.innerHTML = '';
+//     if(PRODUCT_BASE.length > 0){
+//         PRODUCT_BASE.forEach(p => cards.appendChild(createProductCard(p)));
+//     }else{
+//         cards.innerHTML = "<p>No Products Found.</p>"
+//     }
+// }
+
+
+// Cards layout
+function renderProductsCards(items = getCurrentSlice()) {
+  const cards = document.getElementById('cardsContainer');
+  cards.innerHTML = '';
+  if(items.length > 0){
+    items.forEach(p => cards.appendChild(createProductCard(p)));
+  }else{
+      cards.innerHTML = "<p>No Products Found.</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.src = mediaUrl+"media/images/no-product.png" };
+    // 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');
+//     if(PRODUCT_BASE.length > 0){
+//         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.innerHTML = 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);
+//         });
+//     }else{
+//         const tr = el('tr'); 
+//         // tr.id = `row-${p.id}`;
+//         const tdName = el('td');
+//         tdName.colSpan = 6;
+//         tdName.innerHTML = "No Products Found."
+//         tr.appendChild(tdName);
+//         // tr.colspan = 6;
+//         // tr.innerHTML 
+//         tbody.appendChild(tr);
+//     }
+
+//     table.appendChild(tbody);
+//     wrap.appendChild(table);
+// }
+
+// --- Inline attributes rendering ---
+
+
+// Table layout
+function renderProductsTable(items = getCurrentSlice()) {
+  const wrap = document.getElementById('tableContainer');
+  wrap.innerHTML = '';
+  const table = document.createElement('table');
+  const thead = document.createElement('thead'); const trh = document.createElement('tr');
+  ['Select', 'Image', 'Product', 'SKU', 'Type', 'Short Description'].forEach(h => {
+    const th = document.createElement('th'); th.textContent = h; trh.appendChild(th);
+  });
+  thead.appendChild(trh); table.appendChild(thead);
+  const tbody = document.createElement('tbody');
+  if(items.length > 0 ){
+    items.forEach(p => {
+        const tr = document.createElement('tr'); tr.id = `row-${p.id}`;
+        const tdSel = document.createElement('td'); tdSel.className = 'select-cell';
+        const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = selectedIds.has(p.item_id);
+        cb.addEventListener('change', () => { setChecked(p.item_id, cb.checked); tr.classList.toggle('selected', cb.checked); });
+        tdSel.appendChild(cb); tr.appendChild(tdSel);
+
+        const tdImg = document.createElement('td'); tdImg.className = 'thumb-cell'; tdImg.appendChild(createMiniThumb(p)); tr.appendChild(tdImg);
+        const tdName = document.createElement('td'); tdName.textContent = p.product_name || '—'; tr.appendChild(tdName);
+        const tdSku  = document.createElement('td'); tdSku.textContent = p.item_id || '—'; tr.appendChild(tdSku);
+        const tdType = document.createElement('td'); const b = document.createElement('span'); b.className = 'badge'; b.textContent = p.product_type || '—'; tdType.appendChild(b); tr.appendChild(tdType);
+        const tdDesc = document.createElement('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);
+    });
+  }else{
+    const tr = el('tr'); 
+    // tr.id = `row-${p.id}`;
+    const tdName = el('td');
+    tdName.colSpan = 6;
+    tdName.innerHTML = "No Products Found."
+    tr.appendChild(tdName);
+    // tr.colspan = 6;
+    // tr.innerHTML 
+    tbody.appendChild(tr);
+  }
+
+  table.appendChild(tbody);
+  wrap.appendChild(table);
+}
+
+
+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.item_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;
+    $('#api-summary').style.display = 'block';
+    
+    
+}
+
+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.item_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';
+    console.log("PRODUCT_BASE",PRODUCT_BASE);
+    renderProductsCards();
+    } else {
+    $('#cardsContainer').style.display = 'none';
+    $('#tableContainer').style.display = '';
+    renderProductsTable();
+    }
+    updateSelectionInfo();
+    renderPagination();               
+
+    // If there is a selection, re-render inline attributes (persist across toggle)
+    if (selectedIds.size > 0) renderInlineAttributes();
+}
+
+// --- Submit & Reset ---
+function submitAttributes() {
+  // Check the length of the new array
+    if (selectedProductsWithAttributes.length === 0) { 
+        alert('Please select at least one product.'); 
+        return; 
+    } 
+    // if (selectedIds.size === 0) { alert('Please select at least one product.'); return; }
+    console.log("selectedIds",selectedIds);
+    jQuery('#full-page-loader').show();  
+    // let inputArray = {
+    //       "product_ids" : [...selectedIds]
+    //     }
+    const extractAdditional = document.getElementById('extract_additional').checked;
+    const processImage = document.getElementById('process_image').checked;
+
+    // Transform the new state array into the required API format
+    const itemIds = selectedProductsWithAttributes.map(p => p.item_id);
+    
+    // Create the mandatory_attrs map: { item_id: { attr_name: [values] } }
+    // NOTE: The backend API you showed expects a flattened list of "mandatory_attrs"
+    // like: { "color": ["color", "shade"], "size": ["size", "fit"] } 
+    // It seems to ignore the selected product-specific values and uses a general list of synonyms.
+    // Assuming the request needs a general map of *all unique* selected attributes across all selected products:
+    
+    let mandatoryAttrsMap = {};
+    selectedProductsWithAttributes.forEach(product => {
+        // Merge attributes from all selected products
+        Object.assign(mandatoryAttrsMap, product.mandatory_attrs);
+    });
+
+    // If the API expects the complex, product-specific payload from your Q1 example:
+    const payloadForQ1 = selectedProductsWithAttributes.map(p => ({
+        item_id: p.item_id,
+        mandatory_attrs: p.mandatory_attrs
+    }));
+
+    let inputArray = {
+              "products": payloadForQ1,
+              
+              "model": "llama-3.1-8b-instant",
+              "extract_additional": extractAdditional,
+              "process_image": processImage
+            }
+    let raw = JSON.stringify(inputArray);
+     fetch('/attr/batch-extract/', {
+        method: 'POST', // or 'POST' if your API expects POST
+        headers: {
+            'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || '',
+            'Content-Type': "application/json"
+        },
+        body: raw
+    })
+    .then(response => response.json())
+    .then(data => {
+        console.log("response data",data); 
+        FAKE_API_RESPONSE = data;   
+        renderInlineAttributes();
+        jQuery('#full-page-loader').hide();  
+     });
+}
+
+function resetAll() {
+  selectedProductsWithAttributes = []; // Reset the main array
+    // selectedIds.clear();
+    lastSeen.clear();
+    renderProducts();
+    // Clear summary
+    document.getElementById('statTotal').textContent = '0';
+    document.getElementById('statOk').textContent = '0';
+    document.getElementById('statKo').textContent = '0';
+    $('#api-summary').style.display = 'none';
+}
+
+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();
+}
+
+
+// Upload elements (Bootstrap modal version)
+const uploadModalEl = document.getElementById('uploadModal');
+const dropzone      = document.getElementById('dropzone');
+const uploadFiles   = document.getElementById('uploadFiles');
+const fileInfo      = document.getElementById('fileInfo');
+const uploadBar     = document.getElementById('uploadBar');
+const uploadStatus  = document.getElementById('uploadStatus');
+
+// Reset modal on show
+uploadModalEl.addEventListener('shown.bs.modal', () => {
+  uploadStatus.textContent = '';
+  uploadStatus.className = '';             // clear success/error class
+  uploadBar.style.width = '0%';
+  uploadBar.setAttribute('aria-valuenow', '0');
+  uploadFiles.value = '';
+  uploadFiles.setAttribute('accept', ACCEPT_TYPES);
+  fileInfo.textContent = 'No files selected.';
+});
+
+function describeFiles(list) {
+  if (!list || list.length === 0) { fileInfo.textContent = 'No files selected.'; return; }
+  const names = Array.from(list).map(f => `${f.name} (${Math.round(f.size/1024)} KB)`);
+  fileInfo.textContent = names.join(', ');
+}
+
+// Drag & drop feedback
+['dragenter','dragover'].forEach(evt => {
+  dropzone.addEventListener(evt, e => { e.preventDefault(); e.stopPropagation(); dropzone.classList.add('drag'); });
+});
+['dragleave','drop'].forEach(evt => {
+  dropzone.addEventListener(evt, e => { e.preventDefault(); e.stopPropagation(); dropzone.classList.remove('drag'); });
+});
+
+// Handle drop
+dropzone.addEventListener('drop', e => {
+  uploadFiles.files = e.dataTransfer.files;
+  describeFiles(uploadFiles.files);
+});
+
+// Click to browse
+// dropzone.addEventListener('click', () => uploadFiles.click());
+
+// Picker change
+uploadFiles.addEventListener('change', () => describeFiles(uploadFiles.files));
+
+function startUpload() {
+  const files = uploadFiles.files;
+  if (!files || files.length === 0) { alert('Please select file(s) to upload.'); return; }
+  jQuery('#full-page-loader').show();
+  uploadStatus.textContent = 'Uploading...';
+  uploadStatus.className = ''; // neutral
+  uploadBar.style.width = '0%';
+  uploadBar.setAttribute('aria-valuenow', '0');
+
+  const form = new FormData();
+  Array.from(files).forEach(f => form.append('file', f));
+  // form.append('uploaded_by', 'Vishal'); // example extra field
+
+  const xhr = new XMLHttpRequest();
+  xhr.open('POST', UPLOAD_API_URL, true);
+  // If you need auth:
+  // xhr.setRequestHeader('Authorization', 'Bearer <token>');
+
+  xhr.upload.onprogress = (e) => {
+    if (e.lengthComputable) {
+      const pct = Math.round((e.loaded / e.total) * 100);
+      uploadBar.style.width = pct + '%';
+      uploadBar.setAttribute('aria-valuenow', String(pct));
+    }
+  };
+
+  xhr.onreadystatechange = () => {
+    if (xhr.readyState === 4) {
+      const ok = (xhr.status >= 200 && xhr.status < 300);
+      try {
+        const resp = JSON.parse(xhr.responseText || '{}');
+        uploadStatus.textContent = ok ? (resp.message || 'Upload successful') : (resp.error || `Upload failed (${xhr.status})`);
+      } catch {
+        uploadStatus.textContent = ok ? 'Upload successful' : `Upload failed (${xhr.status})`;
+      }
+      uploadStatus.className = ok ? 'success' : 'error';
+      // Optional: auto-close the modal on success after 1.2s:
+      // if (ok) setTimeout(() => bootstrap.Modal.getInstance(uploadModalEl).hide(), 1200);
+    }
+  };
+
+  xhr.onerror = () => {
+    uploadStatus.textContent = 'Network error during upload.';
+    uploadStatus.className = 'error';
+  };
+
+  xhr.send(form);
+  setTimeout(()=>{
+      jQuery('#uploadModal').modal('hide');
+  },3000)
+  jQuery('#full-page-loader').hide();
+
+}
+
+// Wire Start button
+document.getElementById('uploadStart').addEventListener('click', startUpload);
+// Cancel button already closes the modal via data-bs-dismiss
+
+
+
+
+// --- Pagination state ---
+let page = 1;
+let pageSize = 5; // default rows per page
+
+function totalPages() {
+  return Math.max(1, Math.ceil(PRODUCT_BASE.length / pageSize));
+}
+
+function clampPage() {
+  page = Math.min(Math.max(1, page), totalPages());
+}
+
+function getCurrentSlice() {
+  clampPage();
+  const start = (page - 1) * pageSize;
+  return PRODUCT_BASE.slice(start, start + pageSize);
+}
+
+function renderPagination() {
+  const bar = document.getElementById('paginationBar');
+  if (!bar) return;
+
+  const tp = totalPages();
+  clampPage();
+
+  bar.innerHTML = `
+    <div class="page-size">
+      <label for="pageSizeSelect">Rows per page</label>
+      <select id="pageSizeSelect">
+        <option value="5"  ${pageSize===5  ? 'selected' : ''}>5</option>
+        <option value="10" ${pageSize===10 ? 'selected' : ''}>10</option>
+        <option value="20" ${pageSize===20 ? 'selected' : ''}>20</option>
+        <option value="50" ${pageSize===50 ? 'selected' : ''}>50</option>
+        <option value="all" ${pageSize>=PRODUCT_BASE.length ? 'selected' : ''}>All</option>
+      </select>
+    </div>
+
+    <div class="pager">
+      <button class="pager-btn" id="prevPage" ${page<=1 ? 'disabled' : ''} aria-label="Previous page">‹</button>
+      <span class="page-info">Page ${page} of ${tp}</span>
+      <button class="pager-btn" id="nextPage" ${page>=tp ? 'disabled' : ''} aria-label="Next page">›</button>
+    </div>
+  `;
+
+  // wire events
+  document.getElementById('prevPage')?.addEventListener('click', () => { if (page > 1) { page--; renderProducts(); } });
+  document.getElementById('nextPage')?.addEventListener('click', () => { if (page < tp) { page++; renderProducts(); } });
+
+  const sel = document.getElementById('pageSizeSelect');
+  if (sel) {
+    sel.addEventListener('change', () => {
+      const val = sel.value;
+      pageSize = (val === 'all') ? PRODUCT_BASE.length : parseInt(val, 10);
+      page = 1;            // reset to first page when size changes
+      renderProducts();
+    });
+  }
+}
+
+// Function to add/remove product from the state and manage its attributes
+function toggleProductSelection(itemId, isChecked, attributes = {}) {
+    const index = selectedProductsWithAttributes.findIndex(p => p.item_id === itemId);
+
+    if (isChecked) {
+        // If selecting, ensure the product object exists in the array
+        if (index === -1) {
+            selectedProductsWithAttributes.push({
+                item_id: itemId,
+                mandatory_attrs: attributes
+            });
+        } else {
+            // Update attributes if the product is already selected
+            selectedProductsWithAttributes[index].mandatory_attrs = attributes;
+        }
+    } else {
+        // If deselecting, remove the product object from the array
+        if (index !== -1) {
+            selectedProductsWithAttributes.splice(index, 1);
+        }
+    }
+    updateSelectionInfo();
+}
+
+// Function to get the current mandatory attributes for a selected item
+function getSelectedAttributes(itemId) {
+    const productEntry = selectedProductsWithAttributes.find(p => p.item_id === itemId);
+    return productEntry ? productEntry.mandatory_attrs : {};
+}
+
+// Helper to check if a product is selected
+function isProductSelected(itemId) {
+    return selectedProductsWithAttributes.some(p => p.item_id === itemId);
+}
+
+// Helper to check if a specific attribute/value is selected
+function isAttributeValueSelected(itemId, attrName, value) {
+    const attrs = getSelectedAttributes(itemId);
+    const values = attrs[attrName];
+    return values ? values.includes(value) : false; // Default all selected when first loaded
+}
+
+
+
+$('.attribute-select').select2({
+    placeholder: 'Select product attributes'
+});

+ 917 - 0
content_quality_tool_public/static/js/attr-extraction-oldselect.js

@@ -0,0 +1,917 @@
+jQuery.noConflict(); // Release $ to other libraries
+console.log(typeof jQuery);
+// $ = jQuery;
+
+// --- Config ---
+const UPLOAD_API_URL = '/attr/products/upload-excel/'; // TODO: set to your upload endpoint
+const ACCEPT_TYPES = '*'; // e.g., 'image/*,.csv,.xlsx'
+
+var 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' }
+];
+// --- Data ---
+const mediaUrl = "./../";
+
+document.addEventListener('DOMContentLoaded', () => {
+    jQuery('#full-page-loader').show();  
+    fetch('/attr/products', {
+        method: 'GET', // or 'POST' if your API expects POST
+        headers: {
+            'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || ''
+        }
+    })
+    .then(response => response.json())
+    .then(data => {
+        console.log("data",data);
+        // --- Wire up ---
+        PRODUCT_BASE = data;
+        PRODUCT_BASE = PRODUCT_BASE.map((d)=>{return {...d,mandatoryAttributes:["color","size"]}});
+        console.log("PRODUCT_BASE",PRODUCT_BASE);
+        if(PRODUCT_BASE.length > 0){
+            $('#paginationBar').style.display = 'block';
+        }
+        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();
+        // });
+
+        // Replace your existing Select All listener with this:
+          document.getElementById('btnSelectAll').addEventListener('click', () => {
+            // Use the container for the active layout
+            const container = (layoutMode === 'cards')
+              ? document.getElementById('cardsContainer')
+              : document.getElementById('tableContainer');
+
+            // Collect all visible checkboxes
+            const boxes = Array.from(container.querySelectorAll('input[type="checkbox"]'));
+
+            // If every visible checkbox is already checked, we'll deselect; otherwise select all
+            const allChecked = boxes.length > 0 && boxes.every(cb => cb.checked);
+
+            boxes.forEach(cb => {
+              const target = !allChecked; // true to select, false to deselect
+              if (cb.checked !== target) {
+                cb.checked = target;
+                // Trigger your existing "change" handler so selectedIds & row .selected class update
+                cb.dispatchEvent(new Event('change', { bubbles: true }));
+              }
+            });
+
+            // Update the selection pill text (doesn't re-render the list)
+            updateSelectionInfo();
+          });
+        document.getElementById('btnCards').addEventListener('click', () => setLayout('cards'));
+        document.getElementById('btnTable').addEventListener('click', () => setLayout('table'));
+        jQuery('#full-page-loader').hide();
+        // if (data.success) {
+        // }
+    });
+});
+
+var 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();
+// NEW: Array of objects { item_id: string, mandatory_attrs: { [attribute_name]: string[] } }
+let selectedProductsWithAttributes = [];
+let selectedAttributes = new Array();
+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;
+    const count = selectedProductsWithAttributes.length;
+    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(); }
+// function setCheckedAttributes(id,attribute, checked) { if (checked) selectedAttributes.add({id: [attribute]}); else selectedIds.delete({id:[attribute]}); 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');
+    // Check selection using the new helper
+    if (isProductSelected(p.item_id)) row.classList.add('selected');
+    // if (selectedIds.has(p.item_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); };
+    img.onerror = () => { img.src = mediaUrl+"media/images/no-product.png" };
+    left.appendChild(img);
+
+    const mid = el('div', 'meta');
+    const name = el('div', 'name'); name.textContent = p.product_name || '—';
+    const desc = el('div', 'desc'); desc.innerHTML = 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.innerHTML = p.product_long_description || ''; long.style.marginTop = '4px';
+    mid.appendChild(name); mid.appendChild(desc); mid.appendChild(badges); mid.appendChild(long);
+
+
+    // const rightAttribute = el('label', 'select');
+
+    // // Main checkbox: "Select"
+    // const cb_attribute = document.createElement('input');
+    // cb_attribute.type = 'checkbox';
+    // cb_attribute.checked = selectedIds.has(p.item_id);
+    // cb_attribute.addEventListener('change', () => {
+    //   setChecked(p.item_id, cb_attribute.checked);
+    //   row.classList.toggle('selected', cb_attribute.checked);
+    // });
+    // const lbl_attribute = el('span');
+    // lbl_attribute.textContent = 'Select';
+
+    // rightAttribute.appendChild(cb_attribute);
+    // rightAttribute.appendChild(lbl_attribute);
+
+    // // --- New checkbox for mandatory attributes ---
+    // if (p.mandatoryAttributes && p.mandatoryAttributes.length > 0) {
+    //   const attrLabel = el('label', 'select-attr');
+    //   const attrCb = document.createElement('input');
+    //   attrCb.type = 'checkbox';
+    //   attrCb.checked = false;  // Not selected by default
+
+    //   attrCb.addEventListener('change', () => {
+    //     if (attrCb.checked) {
+    //       // Select the main checkbox
+    //       cb_attribute.checked = true;
+    //       setChecked(p.item_id, true);
+    //       row.classList.add('selected');
+    //     } else {
+    //       // Optionally uncheck main if attrCb is unchecked
+    //       // (optional logic)
+    //     }
+    //   });
+
+    //   const attrSpan = el('span');
+    //   attrSpan.textContent = `Select mandatory (${p.mandatoryAttributes.join(', ')})`;
+
+    //   attrLabel.appendChild(attrCb);
+    //   attrLabel.appendChild(attrSpan);
+
+    //   rightAttribute.appendChild(attrLabel);
+    // }
+     
+    // const attri = el('p',"pSelectRight");
+    // attri.innerHTML = "Select Attribute : ";
+    // var secondRight = el('label', 'selectRight');
+    // if (p.mandatoryAttributes && p.mandatoryAttributes.length > 0) {
+      
+    //   p.mandatoryAttributes.forEach((data,index)=>{  
+             
+    //     const cbSeond = document.createElement('input'); 
+    //     cbSeond.type = 'checkbox'; 
+    //     cbSeond.checked = selectedIds.has(p.item_id);
+    //     cbSeond.addEventListener('change', () => { setChecked(p.item_id, cbSeond.checked); 
+    //     row.classList.toggle('selected', cbSeond.checked); });
+        
+    //     const lblsecond = el('span');
+    //     lblsecond.className = "attributeName"; 
+    //     lblsecond.textContent = data ;
+    //     secondRight.appendChild(cbSeond); secondRight.appendChild(lblsecond);
+    //   })
+
+    // }
+
+    
+    // const right = el('label', 'select');
+    // const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = selectedIds.has(p.item_id);
+    // cb.addEventListener('change', () => { setChecked(p.item_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')); } });
+
+
+    // --- Main Select Checkbox (Product Selection) ---
+    const right = el('label', 'select');
+    const cb = document.createElement('input'); cb.type = 'checkbox';
+    cb.checked = isProductSelected(p.item_id);
+    const lbl = el('span'); lbl.textContent = 'Select Product';
+    right.appendChild(cb); right.appendChild(lbl);
+
+    // --- Dynamic Attribute Selects ---
+    const attrContainer = el('div', 'attribute-selectors');
+
+    // Find all mandatory and non-mandatory attributes for this product
+    const mandatoryAttributes = p.product_type_details?.filter(a => a.is_mandatory === 'Yes') || [];
+    const optionalAttributes = p.product_type_details?.filter(a => a.is_mandatory !== 'Yes') || [];
+
+    // Helper to update the main state object with all current selections
+    const updateProductState = () => {
+        const isSelected = cb.checked;
+        const currentSelections = {};
+
+        // Only process attributes if the main product is selected
+        if (isSelected) {
+            // Get selections from the dynamic selects
+            attrContainer.querySelectorAll('select').forEach(select => {
+                const attrName = select.dataset.attrName;
+                const selectedOptions = Array.from(select.options)
+                    .filter(option => option.selected)
+                    .map(option => option.value);
+
+                if (selectedOptions.length > 0) {
+                    currentSelections[attrName] = selectedOptions;
+                }
+            });
+        }
+        
+        // Use toggleProductSelection to update the state
+        toggleProductSelection(p.item_id, isSelected, currentSelections);
+        row.classList.toggle('selected', isSelected);
+    };
+
+    // Attach listener to main checkbox
+    cb.addEventListener('change', () => {
+        // Toggle the visibility/enabled state of the attribute selects
+        attrContainer.classList.toggle('disabled', !cb.checked);
+        updateProductState();
+    });
+    
+    // --- Render Mandatory Attributes ---
+    if (mandatoryAttributes.length > 0) {
+        const manTitle = el('p', "pSelectRight mandatory-title");
+        manTitle.innerHTML = "Mandatory Attributes:";
+        attrContainer.appendChild(manTitle);
+
+        mandatoryAttributes.forEach(attr => {
+            const label = el('label', 'attribute-label');
+            label.textContent = `${attr.attribute_name} (${attr.is_mandatory === 'Yes' ? 'Mandatory' : 'Optional'}):`;
+            
+            const select = el('select', 'attribute-select');
+            select.multiple = true;
+            select.dataset.attrName = attr.attribute_name;
+
+            // Get previously selected values or default to all
+            const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+
+            attr.possible_values.forEach(value => {
+                const option = document.createElement('option');
+                option.value = value;
+                option.textContent = value;
+                // Check if this option should be pre-selected
+                option.selected = initialSelected.includes(value); 
+                select.appendChild(option);
+            });
+
+            select.addEventListener('change', updateProductState); // Update state when a selection changes
+            
+            label.appendChild(select);
+            attrContainer.appendChild(label);
+        });
+    }
+
+    // --- Render Optional Attributes (Same structure, different title) ---
+    if (optionalAttributes.length > 0) {
+        const optTitle = el('p', "pSelectRight optional-title");
+        optTitle.innerHTML = "Optional Attributes:";
+        attrContainer.appendChild(optTitle);
+
+        optionalAttributes.forEach(attr => {
+            const label = el('label', 'attribute-label');
+            label.textContent = `${attr.attribute_name} (Optional):`;
+            
+            const select = el('select', 'attribute-select');
+            select.multiple = true;
+            select.dataset.attrName = attr.attribute_name;
+
+            // Get previously selected values or default to all
+            const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+
+            attr.possible_values.forEach(value => {
+                const option = document.createElement('option');
+                option.value = value;
+                option.textContent = value;
+                // Check if this option should be pre-selected
+                option.selected = initialSelected.includes(value);
+                select.appendChild(option);
+            });
+
+            select.addEventListener('change', updateProductState);
+            
+            label.appendChild(select);
+            attrContainer.appendChild(label);
+        });
+    }
+
+    // Initialize attribute selectors' enabled state and state data
+    attrContainer.classList.toggle('disabled', !cb.checked);
+    // Initial state setup if the product was already selected (e.g., after a re-render)
+    if (cb.checked) {
+        // This is important to set the initial state correctly on load
+        // We defer this until all selects are mounted, or ensure the initial state is correct.
+        // For simplicity, we assume the data from PRODUCT_BASE already includes selected attributes if a selection exists
+        // (which it won't in this case, so they default to all/empty)
+    }
+
+    const inline = el('div', 'attr-inline');
+    inline.dataset.pid = p.item_id; // use item_id for mapping
+
+    row.addEventListener('click', (e) => {
+        // Only toggle main checkbox if an attribute select or its option wasn't clicked
+        if (e.target.tagName.toLowerCase() !== 'input' && e.target.tagName.toLowerCase() !== 'select' && e.target.tagName.toLowerCase() !== 'option') {
+            cb.checked = !cb.checked;
+            cb.dispatchEvent(new Event('change'));
+        }
+    });
+
+    row.appendChild(left); row.appendChild(mid);
+    
+    row.appendChild(right);
+    // if (p.mandatoryAttributes && p.mandatoryAttributes.length > 0) { 
+      // const hr = el('hr');
+      // row.appendChild(hr);
+      // row.appendChild(attri);
+      // row.appendChild(secondRight);
+    // }
+    row.appendChild(attrContainer); // Append the new attribute selectors container
+    row.appendChild(inline);
+    
+    return row;
+}
+
+// function renderProductsCards() {
+//     const cards = $('#cardsContainer');
+//     cards.innerHTML = '';
+//     if(PRODUCT_BASE.length > 0){
+//         PRODUCT_BASE.forEach(p => cards.appendChild(createProductCard(p)));
+//     }else{
+//         cards.innerHTML = "<p>No Products Found.</p>"
+//     }
+// }
+
+
+// Cards layout
+function renderProductsCards(items = getCurrentSlice()) {
+  const cards = document.getElementById('cardsContainer');
+  cards.innerHTML = '';
+  if(items.length > 0){
+    items.forEach(p => cards.appendChild(createProductCard(p)));
+  }else{
+      cards.innerHTML = "<p>No Products Found.</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.src = mediaUrl+"media/images/no-product.png" };
+    // 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');
+//     if(PRODUCT_BASE.length > 0){
+//         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.innerHTML = 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);
+//         });
+//     }else{
+//         const tr = el('tr'); 
+//         // tr.id = `row-${p.id}`;
+//         const tdName = el('td');
+//         tdName.colSpan = 6;
+//         tdName.innerHTML = "No Products Found."
+//         tr.appendChild(tdName);
+//         // tr.colspan = 6;
+//         // tr.innerHTML 
+//         tbody.appendChild(tr);
+//     }
+
+//     table.appendChild(tbody);
+//     wrap.appendChild(table);
+// }
+
+// --- Inline attributes rendering ---
+
+
+// Table layout
+function renderProductsTable(items = getCurrentSlice()) {
+  const wrap = document.getElementById('tableContainer');
+  wrap.innerHTML = '';
+  const table = document.createElement('table');
+  const thead = document.createElement('thead'); const trh = document.createElement('tr');
+  ['Select', 'Image', 'Product', 'SKU', 'Type', 'Short Description'].forEach(h => {
+    const th = document.createElement('th'); th.textContent = h; trh.appendChild(th);
+  });
+  thead.appendChild(trh); table.appendChild(thead);
+  const tbody = document.createElement('tbody');
+  if(items.length > 0 ){
+    items.forEach(p => {
+        const tr = document.createElement('tr'); tr.id = `row-${p.id}`;
+        const tdSel = document.createElement('td'); tdSel.className = 'select-cell';
+        const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = selectedIds.has(p.item_id);
+        cb.addEventListener('change', () => { setChecked(p.item_id, cb.checked); tr.classList.toggle('selected', cb.checked); });
+        tdSel.appendChild(cb); tr.appendChild(tdSel);
+
+        const tdImg = document.createElement('td'); tdImg.className = 'thumb-cell'; tdImg.appendChild(createMiniThumb(p)); tr.appendChild(tdImg);
+        const tdName = document.createElement('td'); tdName.textContent = p.product_name || '—'; tr.appendChild(tdName);
+        const tdSku  = document.createElement('td'); tdSku.textContent = p.item_id || '—'; tr.appendChild(tdSku);
+        const tdType = document.createElement('td'); const b = document.createElement('span'); b.className = 'badge'; b.textContent = p.product_type || '—'; tdType.appendChild(b); tr.appendChild(tdType);
+        const tdDesc = document.createElement('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);
+    });
+  }else{
+    const tr = el('tr'); 
+    // tr.id = `row-${p.id}`;
+    const tdName = el('td');
+    tdName.colSpan = 6;
+    tdName.innerHTML = "No Products Found."
+    tr.appendChild(tdName);
+    // tr.colspan = 6;
+    // tr.innerHTML 
+    tbody.appendChild(tr);
+  }
+
+  table.appendChild(tbody);
+  wrap.appendChild(table);
+}
+
+
+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.item_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;
+    $('#api-summary').style.display = 'block';
+    
+    
+}
+
+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.item_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';
+    console.log("PRODUCT_BASE",PRODUCT_BASE);
+    renderProductsCards();
+    } else {
+    $('#cardsContainer').style.display = 'none';
+    $('#tableContainer').style.display = '';
+    renderProductsTable();
+    }
+    updateSelectionInfo();
+    renderPagination();               
+
+    // If there is a selection, re-render inline attributes (persist across toggle)
+    if (selectedIds.size > 0) renderInlineAttributes();
+}
+
+// --- Submit & Reset ---
+function submitAttributes() {
+  // Check the length of the new array
+    if (selectedProductsWithAttributes.length === 0) { 
+        alert('Please select at least one product.'); 
+        return; 
+    } 
+    // if (selectedIds.size === 0) { alert('Please select at least one product.'); return; }
+    console.log("selectedIds",selectedIds);
+    jQuery('#full-page-loader').show();  
+    // let inputArray = {
+    //       "product_ids" : [...selectedIds]
+    //     }
+    const extractAdditional = document.getElementById('extract_additional').checked;
+    const processImage = document.getElementById('process_image').checked;
+
+    // Transform the new state array into the required API format
+    const itemIds = selectedProductsWithAttributes.map(p => p.item_id);
+    
+    // Create the mandatory_attrs map: { item_id: { attr_name: [values] } }
+    // NOTE: The backend API you showed expects a flattened list of "mandatory_attrs"
+    // like: { "color": ["color", "shade"], "size": ["size", "fit"] } 
+    // It seems to ignore the selected product-specific values and uses a general list of synonyms.
+    // Assuming the request needs a general map of *all unique* selected attributes across all selected products:
+    
+    let mandatoryAttrsMap = {};
+    selectedProductsWithAttributes.forEach(product => {
+        // Merge attributes from all selected products
+        Object.assign(mandatoryAttrsMap, product.mandatory_attrs);
+    });
+
+    // If the API expects the complex, product-specific payload from your Q1 example:
+    const payloadForQ1 = selectedProductsWithAttributes.map(p => ({
+        item_id: p.item_id,
+        mandatory_attrs: p.mandatory_attrs
+    }));
+
+    let inputArray = {
+              "products": payloadForQ1,
+              
+              "model": "llama-3.1-8b-instant",
+              "extract_additional": extractAdditional,
+              "process_image": processImage
+            }
+    let raw = JSON.stringify(inputArray);
+     fetch('/attr/batch-extract/', {
+        method: 'POST', // or 'POST' if your API expects POST
+        headers: {
+            'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || '',
+            'Content-Type': "application/json"
+        },
+        body: raw
+    })
+    .then(response => response.json())
+    .then(data => {
+        console.log("response data",data); 
+        FAKE_API_RESPONSE = data;   
+        renderInlineAttributes();
+        jQuery('#full-page-loader').hide();  
+     });
+}
+
+function resetAll() {
+  selectedProductsWithAttributes = []; // Reset the main array
+    // selectedIds.clear();
+    lastSeen.clear();
+    renderProducts();
+    // Clear summary
+    document.getElementById('statTotal').textContent = '0';
+    document.getElementById('statOk').textContent = '0';
+    document.getElementById('statKo').textContent = '0';
+    $('#api-summary').style.display = 'none';
+}
+
+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();
+}
+
+
+// Upload elements (Bootstrap modal version)
+const uploadModalEl = document.getElementById('uploadModal');
+const dropzone      = document.getElementById('dropzone');
+const uploadFiles   = document.getElementById('uploadFiles');
+const fileInfo      = document.getElementById('fileInfo');
+const uploadBar     = document.getElementById('uploadBar');
+const uploadStatus  = document.getElementById('uploadStatus');
+
+// Reset modal on show
+uploadModalEl.addEventListener('shown.bs.modal', () => {
+  uploadStatus.textContent = '';
+  uploadStatus.className = '';             // clear success/error class
+  uploadBar.style.width = '0%';
+  uploadBar.setAttribute('aria-valuenow', '0');
+  uploadFiles.value = '';
+  uploadFiles.setAttribute('accept', ACCEPT_TYPES);
+  fileInfo.textContent = 'No files selected.';
+});
+
+function describeFiles(list) {
+  if (!list || list.length === 0) { fileInfo.textContent = 'No files selected.'; return; }
+  const names = Array.from(list).map(f => `${f.name} (${Math.round(f.size/1024)} KB)`);
+  fileInfo.textContent = names.join(', ');
+}
+
+// Drag & drop feedback
+['dragenter','dragover'].forEach(evt => {
+  dropzone.addEventListener(evt, e => { e.preventDefault(); e.stopPropagation(); dropzone.classList.add('drag'); });
+});
+['dragleave','drop'].forEach(evt => {
+  dropzone.addEventListener(evt, e => { e.preventDefault(); e.stopPropagation(); dropzone.classList.remove('drag'); });
+});
+
+// Handle drop
+dropzone.addEventListener('drop', e => {
+  uploadFiles.files = e.dataTransfer.files;
+  describeFiles(uploadFiles.files);
+});
+
+// Click to browse
+// dropzone.addEventListener('click', () => uploadFiles.click());
+
+// Picker change
+uploadFiles.addEventListener('change', () => describeFiles(uploadFiles.files));
+
+function startUpload() {
+  const files = uploadFiles.files;
+  if (!files || files.length === 0) { alert('Please select file(s) to upload.'); return; }
+  jQuery('#full-page-loader').show();
+  uploadStatus.textContent = 'Uploading...';
+  uploadStatus.className = ''; // neutral
+  uploadBar.style.width = '0%';
+  uploadBar.setAttribute('aria-valuenow', '0');
+
+  const form = new FormData();
+  Array.from(files).forEach(f => form.append('file', f));
+  // form.append('uploaded_by', 'Vishal'); // example extra field
+
+  const xhr = new XMLHttpRequest();
+  xhr.open('POST', UPLOAD_API_URL, true);
+  // If you need auth:
+  // xhr.setRequestHeader('Authorization', 'Bearer <token>');
+
+  xhr.upload.onprogress = (e) => {
+    if (e.lengthComputable) {
+      const pct = Math.round((e.loaded / e.total) * 100);
+      uploadBar.style.width = pct + '%';
+      uploadBar.setAttribute('aria-valuenow', String(pct));
+    }
+  };
+
+  xhr.onreadystatechange = () => {
+    if (xhr.readyState === 4) {
+      const ok = (xhr.status >= 200 && xhr.status < 300);
+      try {
+        const resp = JSON.parse(xhr.responseText || '{}');
+        uploadStatus.textContent = ok ? (resp.message || 'Upload successful') : (resp.error || `Upload failed (${xhr.status})`);
+      } catch {
+        uploadStatus.textContent = ok ? 'Upload successful' : `Upload failed (${xhr.status})`;
+      }
+      uploadStatus.className = ok ? 'success' : 'error';
+      // Optional: auto-close the modal on success after 1.2s:
+      // if (ok) setTimeout(() => bootstrap.Modal.getInstance(uploadModalEl).hide(), 1200);
+    }
+  };
+
+  xhr.onerror = () => {
+    uploadStatus.textContent = 'Network error during upload.';
+    uploadStatus.className = 'error';
+  };
+
+  xhr.send(form);
+  setTimeout(()=>{
+      jQuery('#uploadModal').modal('hide');
+  },3000)
+  jQuery('#full-page-loader').hide();
+
+}
+
+// Wire Start button
+document.getElementById('uploadStart').addEventListener('click', startUpload);
+// Cancel button already closes the modal via data-bs-dismiss
+
+
+
+
+// --- Pagination state ---
+let page = 1;
+let pageSize = 5; // default rows per page
+
+function totalPages() {
+  return Math.max(1, Math.ceil(PRODUCT_BASE.length / pageSize));
+}
+
+function clampPage() {
+  page = Math.min(Math.max(1, page), totalPages());
+}
+
+function getCurrentSlice() {
+  clampPage();
+  const start = (page - 1) * pageSize;
+  return PRODUCT_BASE.slice(start, start + pageSize);
+}
+
+function renderPagination() {
+  const bar = document.getElementById('paginationBar');
+  if (!bar) return;
+
+  const tp = totalPages();
+  clampPage();
+
+  bar.innerHTML = `
+    <div class="page-size">
+      <label for="pageSizeSelect">Rows per page</label>
+      <select id="pageSizeSelect">
+        <option value="5"  ${pageSize===5  ? 'selected' : ''}>5</option>
+        <option value="10" ${pageSize===10 ? 'selected' : ''}>10</option>
+        <option value="20" ${pageSize===20 ? 'selected' : ''}>20</option>
+        <option value="50" ${pageSize===50 ? 'selected' : ''}>50</option>
+        <option value="all" ${pageSize>=PRODUCT_BASE.length ? 'selected' : ''}>All</option>
+      </select>
+    </div>
+
+    <div class="pager">
+      <button class="pager-btn" id="prevPage" ${page<=1 ? 'disabled' : ''} aria-label="Previous page">‹</button>
+      <span class="page-info">Page ${page} of ${tp}</span>
+      <button class="pager-btn" id="nextPage" ${page>=tp ? 'disabled' : ''} aria-label="Next page">›</button>
+    </div>
+  `;
+
+  // wire events
+  document.getElementById('prevPage')?.addEventListener('click', () => { if (page > 1) { page--; renderProducts(); } });
+  document.getElementById('nextPage')?.addEventListener('click', () => { if (page < tp) { page++; renderProducts(); } });
+
+  const sel = document.getElementById('pageSizeSelect');
+  if (sel) {
+    sel.addEventListener('change', () => {
+      const val = sel.value;
+      pageSize = (val === 'all') ? PRODUCT_BASE.length : parseInt(val, 10);
+      page = 1;            // reset to first page when size changes
+      renderProducts();
+    });
+  }
+}
+
+// Function to add/remove product from the state and manage its attributes
+function toggleProductSelection(itemId, isChecked, attributes = {}) {
+    const index = selectedProductsWithAttributes.findIndex(p => p.item_id === itemId);
+
+    if (isChecked) {
+        // If selecting, ensure the product object exists in the array
+        if (index === -1) {
+            selectedProductsWithAttributes.push({
+                item_id: itemId,
+                mandatory_attrs: attributes
+            });
+        } else {
+            // Update attributes if the product is already selected
+            selectedProductsWithAttributes[index].mandatory_attrs = attributes;
+        }
+    } else {
+        // If deselecting, remove the product object from the array
+        if (index !== -1) {
+            selectedProductsWithAttributes.splice(index, 1);
+        }
+    }
+    updateSelectionInfo();
+}
+
+// Function to get the current mandatory attributes for a selected item
+function getSelectedAttributes(itemId) {
+    const productEntry = selectedProductsWithAttributes.find(p => p.item_id === itemId);
+    return productEntry ? productEntry.mandatory_attrs : {};
+}
+
+// Helper to check if a product is selected
+function isProductSelected(itemId) {
+    return selectedProductsWithAttributes.some(p => p.item_id === itemId);
+}
+
+// Helper to check if a specific attribute/value is selected
+function isAttributeValueSelected(itemId, attrName, value) {
+    const attrs = getSelectedAttributes(itemId);
+    const values = attrs[attrName];
+    return values ? values.includes(value) : false; // Default all selected when first loaded
+}

+ 714 - 0
content_quality_tool_public/static/js/attr-extraction-working.js

@@ -0,0 +1,714 @@
+jQuery.noConflict(); // Release $ to other libraries
+console.log(typeof jQuery);
+// $ = jQuery;
+
+// --- Config ---
+const UPLOAD_API_URL = '/attr/products/upload-excel/'; // TODO: set to your upload endpoint
+const ACCEPT_TYPES = '*'; // e.g., 'image/*,.csv,.xlsx'
+
+var 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' }
+];
+// --- Data ---
+const mediaUrl = "./../";
+
+document.addEventListener('DOMContentLoaded', () => {
+    jQuery('#full-page-loader').show();  
+    fetch('/attr/products', {
+        method: 'GET', // or 'POST' if your API expects POST
+        headers: {
+            'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || ''
+        }
+    })
+    .then(response => response.json())
+    .then(data => {
+        console.log("data",data);
+        // --- Wire up ---
+        PRODUCT_BASE = data;
+        PRODUCT_BASE = PRODUCT_BASE.map((d)=>{return {...d,mandatoryAttributes:["color","size"]}});
+        console.log("PRODUCT_BASE",PRODUCT_BASE);
+        if(PRODUCT_BASE.length > 0){
+            $('#paginationBar').style.display = 'block';
+        }
+        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();
+        // });
+
+        // Replace your existing Select All listener with this:
+          document.getElementById('btnSelectAll').addEventListener('click', () => {
+            // Use the container for the active layout
+            const container = (layoutMode === 'cards')
+              ? document.getElementById('cardsContainer')
+              : document.getElementById('tableContainer');
+
+            // Collect all visible checkboxes
+            const boxes = Array.from(container.querySelectorAll('input[type="checkbox"]'));
+
+            // If every visible checkbox is already checked, we'll deselect; otherwise select all
+            const allChecked = boxes.length > 0 && boxes.every(cb => cb.checked);
+
+            boxes.forEach(cb => {
+              const target = !allChecked; // true to select, false to deselect
+              if (cb.checked !== target) {
+                cb.checked = target;
+                // Trigger your existing "change" handler so selectedIds & row .selected class update
+                cb.dispatchEvent(new Event('change', { bubbles: true }));
+              }
+            });
+
+            // Update the selection pill text (doesn't re-render the list)
+            updateSelectionInfo();
+          });
+        document.getElementById('btnCards').addEventListener('click', () => setLayout('cards'));
+        document.getElementById('btnTable').addEventListener('click', () => setLayout('table'));
+        jQuery('#full-page-loader').hide();
+        // if (data.success) {
+        // }
+    });
+});
+
+var 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();
+let selectedAttributes = new Array();
+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(); }
+// function setCheckedAttributes(id,attribute, checked) { if (checked) selectedAttributes.add({id: [attribute]}); else selectedIds.delete({id:[attribute]}); 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.item_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); };
+    img.onerror = () => { img.src = mediaUrl+"media/images/no-product.png" };
+    left.appendChild(img);
+
+    const mid = el('div', 'meta');
+    const name = el('div', 'name'); name.textContent = p.product_name || '—';
+    const desc = el('div', 'desc'); desc.innerHTML = 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.innerHTML = p.product_long_description || ''; long.style.marginTop = '4px';
+    mid.appendChild(name); mid.appendChild(desc); mid.appendChild(badges); mid.appendChild(long);
+
+
+    // const rightAttribute = el('label', 'select');
+
+    // // Main checkbox: "Select"
+    // const cb_attribute = document.createElement('input');
+    // cb_attribute.type = 'checkbox';
+    // cb_attribute.checked = selectedIds.has(p.item_id);
+    // cb_attribute.addEventListener('change', () => {
+    //   setChecked(p.item_id, cb_attribute.checked);
+    //   row.classList.toggle('selected', cb_attribute.checked);
+    // });
+    // const lbl_attribute = el('span');
+    // lbl_attribute.textContent = 'Select';
+
+    // rightAttribute.appendChild(cb_attribute);
+    // rightAttribute.appendChild(lbl_attribute);
+
+    // // --- New checkbox for mandatory attributes ---
+    // if (p.mandatoryAttributes && p.mandatoryAttributes.length > 0) {
+    //   const attrLabel = el('label', 'select-attr');
+    //   const attrCb = document.createElement('input');
+    //   attrCb.type = 'checkbox';
+    //   attrCb.checked = false;  // Not selected by default
+
+    //   attrCb.addEventListener('change', () => {
+    //     if (attrCb.checked) {
+    //       // Select the main checkbox
+    //       cb_attribute.checked = true;
+    //       setChecked(p.item_id, true);
+    //       row.classList.add('selected');
+    //     } else {
+    //       // Optionally uncheck main if attrCb is unchecked
+    //       // (optional logic)
+    //     }
+    //   });
+
+    //   const attrSpan = el('span');
+    //   attrSpan.textContent = `Select mandatory (${p.mandatoryAttributes.join(', ')})`;
+
+    //   attrLabel.appendChild(attrCb);
+    //   attrLabel.appendChild(attrSpan);
+
+    //   rightAttribute.appendChild(attrLabel);
+    // }
+     
+    const attri = el('p',"pSelectRight");
+    attri.innerHTML = "Select Attribute : ";
+    var secondRight = el('label', 'selectRight');
+    if (p.mandatoryAttributes && p.mandatoryAttributes.length > 0) {
+      
+      p.mandatoryAttributes.forEach((data,index)=>{  
+             
+        const cbSeond = document.createElement('input'); 
+        cbSeond.type = 'checkbox'; 
+        cbSeond.checked = selectedIds.has(p.item_id);
+        cbSeond.addEventListener('change', () => { setChecked(p.item_id, cbSeond.checked); 
+        row.classList.toggle('selected', cbSeond.checked); });
+        
+        const lblsecond = el('span');
+        lblsecond.className = "attributeName"; 
+        lblsecond.textContent = data;
+        secondRight.appendChild(cbSeond); secondRight.appendChild(lblsecond);
+      })
+
+    }
+
+    
+    const right = el('label', 'select');
+    const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = selectedIds.has(p.item_id);
+    cb.addEventListener('change', () => { setChecked(p.item_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);
+    if (p.mandatoryAttributes && p.mandatoryAttributes.length > 0) { 
+      // const hr = el('hr');
+      // row.appendChild(hr);
+      row.appendChild(attri);
+      row.appendChild(secondRight);
+    }
+    row.appendChild(inline);
+    
+    return row;
+}
+
+// function renderProductsCards() {
+//     const cards = $('#cardsContainer');
+//     cards.innerHTML = '';
+//     if(PRODUCT_BASE.length > 0){
+//         PRODUCT_BASE.forEach(p => cards.appendChild(createProductCard(p)));
+//     }else{
+//         cards.innerHTML = "<p>No Products Found.</p>"
+//     }
+// }
+
+
+// Cards layout
+function renderProductsCards(items = getCurrentSlice()) {
+  const cards = document.getElementById('cardsContainer');
+  cards.innerHTML = '';
+  if(items.length > 0){
+    items.forEach(p => cards.appendChild(createProductCard(p)));
+  }else{
+      cards.innerHTML = "<p>No Products Found.</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.src = mediaUrl+"media/images/no-product.png" };
+    // 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');
+//     if(PRODUCT_BASE.length > 0){
+//         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.innerHTML = 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);
+//         });
+//     }else{
+//         const tr = el('tr'); 
+//         // tr.id = `row-${p.id}`;
+//         const tdName = el('td');
+//         tdName.colSpan = 6;
+//         tdName.innerHTML = "No Products Found."
+//         tr.appendChild(tdName);
+//         // tr.colspan = 6;
+//         // tr.innerHTML 
+//         tbody.appendChild(tr);
+//     }
+
+//     table.appendChild(tbody);
+//     wrap.appendChild(table);
+// }
+
+// --- Inline attributes rendering ---
+
+
+// Table layout
+function renderProductsTable(items = getCurrentSlice()) {
+  const wrap = document.getElementById('tableContainer');
+  wrap.innerHTML = '';
+  const table = document.createElement('table');
+  const thead = document.createElement('thead'); const trh = document.createElement('tr');
+  ['Select', 'Image', 'Product', 'SKU', 'Type', 'Short Description'].forEach(h => {
+    const th = document.createElement('th'); th.textContent = h; trh.appendChild(th);
+  });
+  thead.appendChild(trh); table.appendChild(thead);
+  const tbody = document.createElement('tbody');
+  if(items.length > 0 ){
+    items.forEach(p => {
+        const tr = document.createElement('tr'); tr.id = `row-${p.id}`;
+        const tdSel = document.createElement('td'); tdSel.className = 'select-cell';
+        const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = selectedIds.has(p.item_id);
+        cb.addEventListener('change', () => { setChecked(p.item_id, cb.checked); tr.classList.toggle('selected', cb.checked); });
+        tdSel.appendChild(cb); tr.appendChild(tdSel);
+
+        const tdImg = document.createElement('td'); tdImg.className = 'thumb-cell'; tdImg.appendChild(createMiniThumb(p)); tr.appendChild(tdImg);
+        const tdName = document.createElement('td'); tdName.textContent = p.product_name || '—'; tr.appendChild(tdName);
+        const tdSku  = document.createElement('td'); tdSku.textContent = p.item_id || '—'; tr.appendChild(tdSku);
+        const tdType = document.createElement('td'); const b = document.createElement('span'); b.className = 'badge'; b.textContent = p.product_type || '—'; tdType.appendChild(b); tr.appendChild(tdType);
+        const tdDesc = document.createElement('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);
+    });
+  }else{
+    const tr = el('tr'); 
+    // tr.id = `row-${p.id}`;
+    const tdName = el('td');
+    tdName.colSpan = 6;
+    tdName.innerHTML = "No Products Found."
+    tr.appendChild(tdName);
+    // tr.colspan = 6;
+    // tr.innerHTML 
+    tbody.appendChild(tr);
+  }
+
+  table.appendChild(tbody);
+  wrap.appendChild(table);
+}
+
+
+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.item_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;
+    $('#api-summary').style.display = 'block';
+    
+    
+}
+
+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.item_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';
+    console.log("PRODUCT_BASE",PRODUCT_BASE);
+    renderProductsCards();
+    } else {
+    $('#cardsContainer').style.display = 'none';
+    $('#tableContainer').style.display = '';
+    renderProductsTable();
+    }
+    updateSelectionInfo();
+    renderPagination();               
+
+    // 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; }
+    console.log("selectedIds",selectedIds);
+    jQuery('#full-page-loader').show();  
+    // let inputArray = {
+    //       "product_ids" : [...selectedIds]
+    //     }
+    const extractAdditional = document.getElementById('extract_additional').checked;
+    const processImage = document.getElementById('process_image').checked;
+
+    let inputArray = {
+              "item_ids": [...selectedIds],
+              "mandatory_attrs": {
+                "color": ["color", "shade"],
+                "size": ["size", "fit"],
+                "fabric": ["fabric", "material"]
+              },
+              "model": "llama-3.1-8b-instant",
+              "extract_additional": extractAdditional,
+              "process_image": processImage
+            }
+    let raw = JSON.stringify(inputArray);
+     fetch('/attr/batch-extract/', {
+        method: 'POST', // or 'POST' if your API expects POST
+        headers: {
+            'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || '',
+            'Content-Type': "application/json"
+        },
+        body: raw
+    })
+    .then(response => response.json())
+    .then(data => {
+        console.log("response data",data); 
+        FAKE_API_RESPONSE = data;   
+        renderInlineAttributes();
+        jQuery('#full-page-loader').hide();  
+     });
+}
+
+function resetAll() {
+    selectedIds.clear();
+    lastSeen.clear();
+    renderProducts();
+    // Clear summary
+    document.getElementById('statTotal').textContent = '0';
+    document.getElementById('statOk').textContent = '0';
+    document.getElementById('statKo').textContent = '0';
+    $('#api-summary').style.display = 'none';
+}
+
+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();
+}
+
+
+// Upload elements (Bootstrap modal version)
+const uploadModalEl = document.getElementById('uploadModal');
+const dropzone      = document.getElementById('dropzone');
+const uploadFiles   = document.getElementById('uploadFiles');
+const fileInfo      = document.getElementById('fileInfo');
+const uploadBar     = document.getElementById('uploadBar');
+const uploadStatus  = document.getElementById('uploadStatus');
+
+// Reset modal on show
+uploadModalEl.addEventListener('shown.bs.modal', () => {
+  uploadStatus.textContent = '';
+  uploadStatus.className = '';             // clear success/error class
+  uploadBar.style.width = '0%';
+  uploadBar.setAttribute('aria-valuenow', '0');
+  uploadFiles.value = '';
+  uploadFiles.setAttribute('accept', ACCEPT_TYPES);
+  fileInfo.textContent = 'No files selected.';
+});
+
+function describeFiles(list) {
+  if (!list || list.length === 0) { fileInfo.textContent = 'No files selected.'; return; }
+  const names = Array.from(list).map(f => `${f.name} (${Math.round(f.size/1024)} KB)`);
+  fileInfo.textContent = names.join(', ');
+}
+
+// Drag & drop feedback
+['dragenter','dragover'].forEach(evt => {
+  dropzone.addEventListener(evt, e => { e.preventDefault(); e.stopPropagation(); dropzone.classList.add('drag'); });
+});
+['dragleave','drop'].forEach(evt => {
+  dropzone.addEventListener(evt, e => { e.preventDefault(); e.stopPropagation(); dropzone.classList.remove('drag'); });
+});
+
+// Handle drop
+dropzone.addEventListener('drop', e => {
+  uploadFiles.files = e.dataTransfer.files;
+  describeFiles(uploadFiles.files);
+});
+
+// Click to browse
+// dropzone.addEventListener('click', () => uploadFiles.click());
+
+// Picker change
+uploadFiles.addEventListener('change', () => describeFiles(uploadFiles.files));
+
+function startUpload() {
+  const files = uploadFiles.files;
+  if (!files || files.length === 0) { alert('Please select file(s) to upload.'); return; }
+  jQuery('#full-page-loader').show();
+  uploadStatus.textContent = 'Uploading...';
+  uploadStatus.className = ''; // neutral
+  uploadBar.style.width = '0%';
+  uploadBar.setAttribute('aria-valuenow', '0');
+
+  const form = new FormData();
+  Array.from(files).forEach(f => form.append('file', f));
+  // form.append('uploaded_by', 'Vishal'); // example extra field
+
+  const xhr = new XMLHttpRequest();
+  xhr.open('POST', UPLOAD_API_URL, true);
+  // If you need auth:
+  // xhr.setRequestHeader('Authorization', 'Bearer <token>');
+
+  xhr.upload.onprogress = (e) => {
+    if (e.lengthComputable) {
+      const pct = Math.round((e.loaded / e.total) * 100);
+      uploadBar.style.width = pct + '%';
+      uploadBar.setAttribute('aria-valuenow', String(pct));
+    }
+  };
+
+  xhr.onreadystatechange = () => {
+    if (xhr.readyState === 4) {
+      const ok = (xhr.status >= 200 && xhr.status < 300);
+      try {
+        const resp = JSON.parse(xhr.responseText || '{}');
+        uploadStatus.textContent = ok ? (resp.message || 'Upload successful') : (resp.error || `Upload failed (${xhr.status})`);
+      } catch {
+        uploadStatus.textContent = ok ? 'Upload successful' : `Upload failed (${xhr.status})`;
+      }
+      uploadStatus.className = ok ? 'success' : 'error';
+      // Optional: auto-close the modal on success after 1.2s:
+      // if (ok) setTimeout(() => bootstrap.Modal.getInstance(uploadModalEl).hide(), 1200);
+    }
+  };
+
+  xhr.onerror = () => {
+    uploadStatus.textContent = 'Network error during upload.';
+    uploadStatus.className = 'error';
+  };
+
+  xhr.send(form);
+  setTimeout(()=>{
+      jQuery('#uploadModal').modal('hide');
+  },3000)
+  jQuery('#full-page-loader').hide();
+
+}
+
+// Wire Start button
+document.getElementById('uploadStart').addEventListener('click', startUpload);
+// Cancel button already closes the modal via data-bs-dismiss
+
+
+
+
+// --- Pagination state ---
+let page = 1;
+let pageSize = 5; // default rows per page
+
+function totalPages() {
+  return Math.max(1, Math.ceil(PRODUCT_BASE.length / pageSize));
+}
+
+function clampPage() {
+  page = Math.min(Math.max(1, page), totalPages());
+}
+
+function getCurrentSlice() {
+  clampPage();
+  const start = (page - 1) * pageSize;
+  return PRODUCT_BASE.slice(start, start + pageSize);
+}
+
+function renderPagination() {
+  const bar = document.getElementById('paginationBar');
+  if (!bar) return;
+
+  const tp = totalPages();
+  clampPage();
+
+  bar.innerHTML = `
+    <div class="page-size">
+      <label for="pageSizeSelect">Rows per page</label>
+      <select id="pageSizeSelect">
+        <option value="5"  ${pageSize===5  ? 'selected' : ''}>5</option>
+        <option value="10" ${pageSize===10 ? 'selected' : ''}>10</option>
+        <option value="20" ${pageSize===20 ? 'selected' : ''}>20</option>
+        <option value="50" ${pageSize===50 ? 'selected' : ''}>50</option>
+        <option value="all" ${pageSize>=PRODUCT_BASE.length ? 'selected' : ''}>All</option>
+      </select>
+    </div>
+
+    <div class="pager">
+      <button class="pager-btn" id="prevPage" ${page<=1 ? 'disabled' : ''} aria-label="Previous page">‹</button>
+      <span class="page-info">Page ${page} of ${tp}</span>
+      <button class="pager-btn" id="nextPage" ${page>=tp ? 'disabled' : ''} aria-label="Next page">›</button>
+    </div>
+  `;
+
+  // wire events
+  document.getElementById('prevPage')?.addEventListener('click', () => { if (page > 1) { page--; renderProducts(); } });
+  document.getElementById('nextPage')?.addEventListener('click', () => { if (page < tp) { page++; renderProducts(); } });
+
+  const sel = document.getElementById('pageSizeSelect');
+  if (sel) {
+    sel.addEventListener('change', () => {
+      const val = sel.value;
+      pageSize = (val === 'all') ? PRODUCT_BASE.length : parseInt(val, 10);
+      page = 1;            // reset to first page when size changes
+      renderProducts();
+    });
+  }
+}
+
+

+ 574 - 218
content_quality_tool_public/static/js/attr-extraction.js

@@ -90,6 +90,9 @@ var FAKE_API_RESPONSE = {
 
 // --- State ---
 let selectedIds = new Set();
+// NEW: Array of objects { item_id: string, mandatory_attrs: { [attribute_name]: string[] } }
+let selectedProductsWithAttributes = [];
+let selectedAttributes = new Array();
 const lastSeen = new Map(); // per-product memory for NEW highlighting (product_id -> maps)
 let layoutMode = 'cards'; // 'cards' | 'table'
 
@@ -100,11 +103,13 @@ const el = (tag, cls) => { const e = document.createElement(tag); if (cls) e.cla
 function updateSelectionInfo() {
     const pill = $('#selectionInfo');
     const total = PRODUCT_BASE.length;
-    const count = selectedIds.size;
+    // const count = selectedIds.size;
+    const count = selectedProductsWithAttributes.length;
     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(); }
+// function setCheckedAttributes(id,attribute, checked) { if (checked) selectedAttributes.add({id: [attribute]}); else selectedIds.delete({id:[attribute]}); updateSelectionInfo(); }
 
 // --- Chips rendering ---
 function renderChips(container, obj, memoryMap) {
@@ -129,7 +134,9 @@ function findApiResultForProduct(p, index, api) { return api.results?.find(r =>
 // --- Cards layout ---
 function createProductCard(p) {
     const row = el('div', 'product');
-    if (selectedIds.has(p.item_id)) row.classList.add('selected');
+    // Check selection using the new helper
+    if (isProductSelected(p.item_id)) row.classList.add('selected');
+    // if (selectedIds.has(p.item_id)) row.classList.add('selected');
 
     const left = el('div', 'thumb');
     const img = new Image(); img.src = mediaUrl+p.image_path || p.image || '';
@@ -148,85 +155,142 @@ function createProductCard(p) {
     const long = el('div', 'desc'); long.innerHTML = p.product_long_description || ''; long.style.marginTop = '4px';
     mid.appendChild(name); mid.appendChild(desc); mid.appendChild(badges); mid.appendChild(long);
 
+    // Helper function to create the chip UI for attributes
+    function createAttributeChips(p, attr, initialSelected, isMandatory, updateCallback) {
+        const wrapper = el('div', 'attribute-chip-group');
+        wrapper.dataset.attrName = attr.attribute_name;
+        wrapper.innerHTML = `<p class="attribute-header">${attr.attribute_name} (${isMandatory ? 'Mandatory' : 'Optional'}):</p>`;
+
+        const chipContainer = el('div', 'chips-container');
+
+        attr.possible_values.forEach(value => {
+            const chip = el('label', 'attribute-chip');
+            
+            // Checkbox input is hidden, but drives the selection state
+            const checkbox = document.createElement('input');
+            checkbox.type = 'checkbox';
+            checkbox.value = value;
+            checkbox.name = `${p.item_id}-${attr.attribute_name}`;
+            
+            // Set initial state
+            checkbox.checked = initialSelected.includes(value); 
+            
+            // The visual part of the chip
+            const span = el('span');
+            span.textContent = value;
+            
+            chip.appendChild(checkbox);
+            chip.appendChild(span);
+            chipContainer.appendChild(chip);
+        });
+        
+        // Use event delegation on the container for performance
+        chipContainer.addEventListener('change', updateCallback);
+
+        wrapper.appendChild(chipContainer);
+        return wrapper;
+    }
 
-    // const rightAttribute = el('label', 'select');
-
-    // // Main checkbox: "Select"
-    // const cb_attribute = document.createElement('input');
-    // cb_attribute.type = 'checkbox';
-    // cb_attribute.checked = selectedIds.has(p.item_id);
-    // cb_attribute.addEventListener('change', () => {
-    //   setChecked(p.item_id, cb_attribute.checked);
-    //   row.classList.toggle('selected', cb_attribute.checked);
-    // });
-    // const lbl_attribute = el('span');
-    // lbl_attribute.textContent = 'Select';
-
-    // rightAttribute.appendChild(cb_attribute);
-    // rightAttribute.appendChild(lbl_attribute);
-
-    // // --- New checkbox for mandatory attributes ---
-    // if (p.mandatoryAttributes && p.mandatoryAttributes.length > 0) {
-    //   const attrLabel = el('label', 'select-attr');
-    //   const attrCb = document.createElement('input');
-    //   attrCb.type = 'checkbox';
-    //   attrCb.checked = false;  // Not selected by default
-
-    //   attrCb.addEventListener('change', () => {
-    //     if (attrCb.checked) {
-    //       // Select the main checkbox
-    //       cb_attribute.checked = true;
-    //       setChecked(p.item_id, true);
-    //       row.classList.add('selected');
-    //     } else {
-    //       // Optionally uncheck main if attrCb is unchecked
-    //       // (optional logic)
-    //     }
-    //   });
-
-    //   const attrSpan = el('span');
-    //   attrSpan.textContent = `Select mandatory (${p.mandatoryAttributes.join(', ')})`;
-
-    //   attrLabel.appendChild(attrCb);
-    //   attrLabel.appendChild(attrSpan);
+    // --- Main Select Checkbox (Product Selection) ---
+    const right = el('label', 'select');
+    const cb = document.createElement('input'); cb.type = 'checkbox';
+    cb.checked = isProductSelected(p.item_id);
+    const lbl = el('span'); lbl.textContent = 'Select Product';
+    right.appendChild(cb); right.appendChild(lbl);
 
-    //   rightAttribute.appendChild(attrLabel);
-    // }
+    // --- Dynamic Attribute Selects ---
+    const attrContainer = el('div', 'attribute-selectors');
+
+    // Find all mandatory and non-mandatory attributes for this product
+    const mandatoryAttributes = p.product_type_details?.filter(a => a.is_mandatory === 'Yes') || [];
+    const optionalAttributes = p.product_type_details?.filter(a => a.is_mandatory !== 'Yes') || [];
+
+    // Helper to update the main state object with all current selections
+    const updateProductState = () => {
+        const isSelected = cb.checked;
+        const currentSelections = {};
+
+        if (isSelected) {
+            // Iterate over all attribute groups (Mandatory and Optional)
+            attrContainer.querySelectorAll('.attribute-chip-group').forEach(group => {
+                const attrName = group.dataset.attrName;
+                
+                // Collect selected chip values
+                const selectedOptions = Array.from(group.querySelectorAll('input[type="checkbox"]:checked'))
+                    .map(checkbox => checkbox.value);
+
+                if (selectedOptions.length > 0) {
+                    currentSelections[attrName] = selectedOptions;
+                }
+            });
+        }
+        
+        toggleProductSelection(p.item_id, isSelected, currentSelections);
+        row.classList.toggle('selected', isSelected);
+    };
+
+    // Attach listener to main checkbox
+    cb.addEventListener('change', () => {
+        attrContainer.classList.toggle('disabled', !cb.checked);
+        updateProductState();
+    });
+    
+    // --- Render Mandatory Attributes ---
+    if (mandatoryAttributes.length > 0) {
+        const manTitle = el('p', "pSelectRight mandatory-title");
+        manTitle.innerHTML = "Mandatory Attributes:";
+        attrContainer.appendChild(manTitle);
+
+        mandatoryAttributes.forEach(attr => {
+            const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+            const chipGroup = createAttributeChips(p, attr, initialSelected, true, updateProductState);
+            attrContainer.appendChild(chipGroup);
+        });
+    }
 
-    const secondRight = el('label', 'select');
-    const cbSeond = document.createElement('input'); cbSeond.type = 'checkbox'; cbSeond.checked = selectedIds.has(p.item_id);
-    cbSeond.addEventListener('change', () => { setChecked(p.item_id, cbSeond.checked); row.classList.toggle('selected', cbSeond.checked); });
-    const lblsecond = el('span'); lblsecond.textContent = 'Select';
-    secondRight.appendChild(cbSeond); secondRight.appendChild(lblsecond);
+    // --- Render Optional Attributes ---
+    if (optionalAttributes.length > 0) {
+      const br = el('br');
+        const optTitle = el('p', "pSelectRight optional-title");
+        optTitle.innerHTML = "Optional Attributes:";
+        attrContainer.appendChild(br);
+        attrContainer.appendChild(optTitle);
+
+        optionalAttributes.forEach(attr => {
+            const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+            const chipGroup = createAttributeChips(p, attr, initialSelected, false, updateProductState);
+            attrContainer.appendChild(chipGroup);
+        });
+    }
 
-    
-    const right = el('label', 'select');
-    const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = selectedIds.has(p.item_id);
-    cb.addEventListener('change', () => { setChecked(p.item_id, cb.checked); row.classList.toggle('selected', cb.checked); });
-    const lbl = el('span'); lbl.textContent = 'Select';
-    right.appendChild(cb); right.appendChild(lbl);
+    // Initialize attribute selectors' enabled state and state data
+    attrContainer.classList.toggle('disabled', !cb.checked);
+    // Initial state setup if the product was already selected (e.g., after a re-render)
+    if (cb.checked) {
+        // This is important to set the initial state correctly on load
+        // We defer this until all selects are mounted, or ensure the initial state is correct.
+        // For simplicity, we assume the data from PRODUCT_BASE already includes selected attributes if a selection exists
+        // (which it won't in this case, so they default to all/empty)
+    }
 
-    // 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(attrContainer); // Append the new attribute selectors container
+    row.appendChild(right);
+    // if (p.mandatoryAttributes && p.mandatoryAttributes.length > 0) { 
+      // const hr = el('hr');
+      // row.appendChild(hr);
+      // row.appendChild(attri);
+      // row.appendChild(secondRight);
+    // }
 
-    row.appendChild(left); row.appendChild(mid); row.appendChild(right);
     row.appendChild(inline);
+    
     return row;
 }
 
-// function renderProductsCards() {
-//     const cards = $('#cardsContainer');
-//     cards.innerHTML = '';
-//     if(PRODUCT_BASE.length > 0){
-//         PRODUCT_BASE.forEach(p => cards.appendChild(createProductCard(p)));
-//     }else{
-//         cards.innerHTML = "<p>No Products Found.</p>"
-//     }
-// }
-
 
 // Cards layout
 function renderProductsCards(items = getCurrentSlice()) {
@@ -251,98 +315,313 @@ function createMiniThumb(p) {
     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');
-//     if(PRODUCT_BASE.length > 0){
-//         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.innerHTML = p.product_short_description || ''; tr.appendChild(tdDesc);
+
+// Table layout
+// function renderProductsTable(items = getCurrentSlice()) {
+//   const wrap = document.getElementById('tableContainer');
+//   wrap.innerHTML = '';
+//   const table = document.createElement('table');
+//   const thead = document.createElement('thead'); const trh = document.createElement('tr');
+//   ['Select', 'Image', 'Product', 'SKU', 'Type', 'Short Description'].forEach(h => {
+//     const th = document.createElement('th'); th.textContent = h; trh.appendChild(th);
+//   });
+//   thead.appendChild(trh); table.appendChild(thead);
+//   const tbody = document.createElement('tbody');
+//   if(items.length > 0 ){
+//     items.forEach(p => {
+//         const tr = document.createElement('tr'); tr.id = `row-${p.id}`;
+//         const tdSel = document.createElement('td'); tdSel.className = 'select-cell';
+//         const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = selectedIds.has(p.item_id);
+//         cb.addEventListener('change', () => { setChecked(p.item_id, cb.checked); tr.classList.toggle('selected', cb.checked); });
+//         tdSel.appendChild(cb); tr.appendChild(tdSel);
+
+//         const tdImg = document.createElement('td'); tdImg.className = 'thumb-cell'; tdImg.appendChild(createMiniThumb(p)); tr.appendChild(tdImg);
+//         const tdName = document.createElement('td'); tdName.textContent = p.product_name || '—'; tr.appendChild(tdName);
+//         const tdSku  = document.createElement('td'); tdSku.textContent = p.item_id || '—'; tr.appendChild(tdSku);
+//         const tdType = document.createElement('td'); const b = document.createElement('span'); b.className = 'badge'; b.textContent = p.product_type || '—'; tdType.appendChild(b); tr.appendChild(tdType);
+//         const tdDesc = document.createElement('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);
-//         });
-//     }else{
-//         const tr = el('tr'); 
-//         // tr.id = `row-${p.id}`;
-//         const tdName = el('td');
-//         tdName.colSpan = 6;
-//         tdName.innerHTML = "No Products Found."
-//         tr.appendChild(tdName);
-//         // tr.colspan = 6;
-//         // tr.innerHTML 
-//         tbody.appendChild(tr);
-//     }
-
-//     table.appendChild(tbody);
-//     wrap.appendChild(table);
+//     });
+//   }else{
+//     const tr = el('tr'); 
+//     // tr.id = `row-${p.id}`;
+//     const tdName = el('td');
+//     tdName.colSpan = 6;
+//     tdName.innerHTML = "No Products Found."
+//     tr.appendChild(tdName);
+//     // tr.colspan = 6;
+//     // tr.innerHTML 
+//     tbody.appendChild(tr);
+//   }
+
+//   table.appendChild(tbody);
+//   wrap.appendChild(table);
 // }
 
-// --- Inline attributes rendering ---
+// NOTE: Ensure getProductStateUpdater and generateAttributeUI functions are defined globally or accessible here.
+
+/**
+ * Returns a closure function that updates the global selectedProductsWithAttributes state
+ * based on the current selections (chips) found in the DOM for a specific product.
+ * This is used for both card and table views.
+ * * @param {Object} p - The product object.
+ * @param {HTMLElement} cb - The main product selection checkbox element.
+ * @param {HTMLElement} tr - The main row/card element (used for toggling 'selected' class).
+ * @returns {function} A function to be used as the attribute change handler.
+ */
+const getProductStateUpdater = (p, cb, tr) => () => {
+    const isSelected = cb.checked;
+    const currentSelections = {};
+    
+    // Find the attribute container using its unique ID, which is the same structure 
+    // used in both card and table detail views (e.g., 'attr-container-124353498' or just the main card element).
+    // For card view, the container is often the attrContainer element itself. 
+    // For table view, we use the explicit ID.
+    const attrContainer = document.getElementById(`attr-container-${p.item_id}`) || tr.querySelector('.attribute-selectors');
+
+    if (isSelected && attrContainer) {
+        // Iterate over all attribute groups (Mandatory and Optional) within the container
+        attrContainer.querySelectorAll('.attribute-chip-group').forEach(group => {
+            const attrName = group.dataset.attrName;
+            
+            // Collect selected chip values
+            const selectedOptions = Array.from(group.querySelectorAll('input[type="checkbox"]:checked'))
+                .map(checkbox => checkbox.value);
+
+            // Only add to the selection if at least one option is selected
+            if (selectedOptions.length > 0) {
+                currentSelections[attrName] = selectedOptions;
+            }
+        });
+    }
+    
+    // Update the global state array (selectedProductsWithAttributes)
+    toggleProductSelection(p.item_id, isSelected, currentSelections);
+    
+    // Update the visual status of the row/card
+    tr.classList.toggle('selected', isSelected);
+};
+
+/**
+ * Generates the full attribute selection UI (chips) for a given product.
+ * NOTE: Assumes el(), createAttributeChips(), and getSelectedAttributes() are defined globally.
+ * @param {Object} p - The product object from PRODUCT_BASE.
+ * @param {function} updateProductState - The callback to run on chip changes.
+ * @param {HTMLElement} attrContainer - The container to append the UI to.
+ */
+function generateAttributeUI(p, updateProductState, attrContainer) {
+    // Clear the container first, just in case
+    attrContainer.innerHTML = ''; 
+
+    const mandatoryAttributes = p.product_type_details?.filter(a => a.is_mandatory === 'Yes') || [];
+    const optionalAttributes = p.product_type_details?.filter(a => a.is_mandatory !== 'Yes') || [];
+
+    // --- Render Mandatory Attributes ---
+    if (mandatoryAttributes.length > 0) {
+        // Use a general title for the section header
+        const manTitle = el('p', "pSelectRight mandatory-title");
+        manTitle.innerHTML = "Mandatory Attributes:";
+        attrContainer.appendChild(manTitle);
+
+        mandatoryAttributes.forEach(attr => {
+            const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+            // The createAttributeChips function must be globally available
+            const chipGroup = createAttributeChips(p, attr, initialSelected, true, updateProductState);
+            attrContainer.appendChild(chipGroup);
+        });
+    }
 
+    // --- Render Optional Attributes ---
+    if (optionalAttributes.length > 0) {
+        // Add visual separation using the optional-title class
+        const optTitle = el('p', "pSelectRight optional-title");
+        optTitle.innerHTML = "Optional Attributes:";
+        
+        // Append the title for separation
+        attrContainer.appendChild(optTitle);
+
+        optionalAttributes.forEach(attr => {
+            const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
+            const chipGroup = createAttributeChips(p, attr, initialSelected, false, updateProductState);
+            attrContainer.appendChild(chipGroup);
+        });
+    }
+}
 
-// Table layout
-function renderProductsTable(items = getCurrentSlice()) {
-  const wrap = document.getElementById('tableContainer');
-  wrap.innerHTML = '';
-  const table = document.createElement('table');
-  const thead = document.createElement('thead'); const trh = document.createElement('tr');
-  ['Select', 'Image', 'Product', 'SKU', 'Type', 'Short Description'].forEach(h => {
-    const th = document.createElement('th'); th.textContent = h; trh.appendChild(th);
-  });
-  thead.appendChild(trh); table.appendChild(thead);
-  const tbody = document.createElement('tbody');
-  if(items.length > 0 ){
-    items.forEach(p => {
-        const tr = document.createElement('tr'); tr.id = `row-${p.id}`;
-        const tdSel = document.createElement('td'); tdSel.className = 'select-cell';
-        const cb = document.createElement('input'); cb.type = 'checkbox'; cb.checked = selectedIds.has(p.item_id);
-        cb.addEventListener('change', () => { setChecked(p.item_id, cb.checked); tr.classList.toggle('selected', cb.checked); });
-        tdSel.appendChild(cb); tr.appendChild(tdSel);
-
-        const tdImg = document.createElement('td'); tdImg.className = 'thumb-cell'; tdImg.appendChild(createMiniThumb(p)); tr.appendChild(tdImg);
-        const tdName = document.createElement('td'); tdName.textContent = p.product_name || '—'; tr.appendChild(tdName);
-        const tdSku  = document.createElement('td'); tdSku.textContent = p.item_id || '—'; tr.appendChild(tdSku);
-        const tdType = document.createElement('td'); const b = document.createElement('span'); b.className = 'badge'; b.textContent = p.product_type || '—'; tdType.appendChild(b); tr.appendChild(tdType);
-        const tdDesc = document.createElement('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);
+/**
+ * Creates the HTML structure for a single attribute group using chip/checkbox labels.
+ * Assumes the helper function 'el' is available.
+ * * @param {Object} p - The product object.
+ * @param {Object} attr - The specific attribute detail object.
+ * @param {string[]} initialSelected - Array of values that should be pre-checked.
+ * @param {boolean} isMandatory - True if the attribute is mandatory.
+ * @param {function} updateCallback - The function to call when a chip selection changes.
+ * @returns {HTMLElement} The attribute chip group container (div).
+ */
+function createAttributeChips(p, attr, initialSelected, isMandatory, updateCallback) {
+    const wrapper = el('div', 'attribute-chip-group');
+    wrapper.dataset.attrName = attr.attribute_name;
+    
+    // Determine the header text based on structure preference (e.g., just the name)
+    const statusText = isMandatory ? ' (Mandatory)' : ' (Optional)';
+    wrapper.innerHTML = `<p class="attribute-header">${attr.attribute_name}${statusText}:</p>`;
+
+    const chipContainer = el('div', 'chips-container');
+
+    attr.possible_values.forEach(value => {
+        const chip = el('label', 'attribute-chip');
+        
+        // Checkbox input is hidden, but drives the selection state
+        const checkbox = document.createElement('input');
+        checkbox.type = 'checkbox';
+        checkbox.value = value;
+        // Ensure the name is unique per product/attribute group
+        checkbox.name = `${p.item_id}-${attr.attribute_name}`; 
+        
+        // Set initial state
+        checkbox.checked = initialSelected.includes(value); 
+        
+        // The visual part of the chip
+        const span = el('span');
+        span.textContent = value;
+        
+        chip.appendChild(checkbox);
+        chip.appendChild(span);
+        chipContainer.appendChild(chip);
     });
-  }else{
-    const tr = el('tr'); 
-    // tr.id = `row-${p.id}`;
-    const tdName = el('td');
-    tdName.colSpan = 6;
-    tdName.innerHTML = "No Products Found."
-    tr.appendChild(tdName);
-    // tr.colspan = 6;
-    // tr.innerHTML 
-    tbody.appendChild(tr);
-  }
+    
+    // Attach listener to the container using event delegation
+    chipContainer.addEventListener('change', updateCallback);
 
-  table.appendChild(tbody);
-  wrap.appendChild(table);
+    wrapper.appendChild(chipContainer);
+    return wrapper;
 }
 
+function renderProductsTable(items = getCurrentSlice()) {
+    const wrap = document.getElementById('tableContainer');
+    wrap.innerHTML = '';
+    const table = document.createElement('table');
+    table.classList.add('table', 'table-striped', 'table-bordered');
+    
+    const thead = document.createElement('thead'); 
+    const trh = document.createElement('tr');
+    
+    // Table Headers
+    ['Select', 'Image', 'Product', 'SKU', 'Type', 'Short Description', 'Attributes'].forEach(h => {
+        const th = document.createElement('th'); th.textContent = h; trh.appendChild(th);
+    });
+    thead.appendChild(trh); table.appendChild(thead);
+    
+    const tbody = document.createElement('tbody');
+    
+    if (items.length > 0) {
+        items.forEach(p => {
+            const tr = document.createElement('tr'); 
+            tr.id = `row-${p.id}`;
+            if (isProductSelected(p.item_id)) tr.classList.add('selected');
+
+            // --- Define Checkbox (cb) and State Updater ---
+            const cb = document.createElement('input'); 
+            cb.type = 'checkbox'; 
+            cb.checked = isProductSelected(p.item_id);
+            
+            // The state updater function is bound to this specific row/checkbox
+            const updateProductState = getProductStateUpdater(p, cb, tr);
+
+            // --- Select Cell ---
+            const tdSel = document.createElement('td'); 
+            tdSel.className = 'select-cell';
+            tdSel.appendChild(cb); 
+            tr.appendChild(tdSel);
+
+            // --- Other Cells ---
+            const tdImg = document.createElement('td'); tdImg.className = 'thumb-cell'; tdImg.appendChild(createMiniThumb(p)); tr.appendChild(tdImg);
+            const tdName = document.createElement('td'); tdName.textContent = p.product_name || '—'; tr.appendChild(tdName);
+            const tdSku  = document.createElement('td'); tdSku.textContent = p.item_id || '—'; tr.appendChild(tdSku);
+            const tdType = document.createElement('td'); const b = document.createElement('span'); b.className = 'badge'; b.textContent = p.product_type || '—'; tdType.appendChild(b); tr.appendChild(tdType);
+            const tdDesc = document.createElement('td'); tdDesc.textContent = p.product_short_description || ''; tr.appendChild(tdDesc);
+
+            // ---------------------------------------------
+            // --- ATTRIBUTE SELECTION IMPLEMENTATION ---
+            // ---------------------------------------------
+
+            // 1. DETAIL ROW STRUCTURE
+            const detailRow = document.createElement('tr');
+            detailRow.classList.add('attribute-detail-row'); // Custom class for styling
+            detailRow.style.display = 'none'; // Initially hidden
+            detailRow.id = `detail-row-${p.id}`;
+            
+            const detailCell = document.createElement('td');
+            detailCell.colSpan = 7; // Must span all columns
+            
+            const attrContainer = document.createElement('div');
+            attrContainer.id = `attr-container-${p.item_id}`; // Unique ID for targeting by updateProductState
+            attrContainer.classList.add('attribute-selectors', 'table-selectors');
+
+            // 2. GENERATE CHIPS UI
+            generateAttributeUI(p, updateProductState, attrContainer);
+
+            // Initially disable the chips if the product is not selected
+            attrContainer.classList.toggle('disabled', !cb.checked);
+
+            detailCell.appendChild(attrContainer);
+            detailRow.appendChild(detailCell);
+
+
+            // 3. TOGGLE BUTTON (in the main row)
+            const tdAttr = document.createElement('td'); 
+            const toggleButton = document.createElement('button');
+            toggleButton.textContent = 'Configure';
+            toggleButton.classList.add('btn', 'btn-sm', 'btn-info', 'attribute-toggle-btn');
+            tdAttr.appendChild(toggleButton);
+            tr.appendChild(tdAttr);
+
+
+            // 4. EVENT LISTENERS
+            
+            // a) Toggle Button Logic
+            toggleButton.addEventListener('click', (e) => {
+                e.stopPropagation(); // Stop row click event
+                const isHidden = detailRow.style.display === 'none';
+                detailRow.style.display = isHidden ? '' : 'none'; // Toggle visibility
+                toggleButton.textContent = isHidden ? 'Hide Attributes' : 'Configure';
+                toggleButton.classList.toggle('btn-info', !isHidden);
+                toggleButton.classList.toggle('btn-secondary', isHidden);
+            });
+            
+            // b) Main Checkbox Change Logic
+            cb.addEventListener('change', () => { 
+                updateProductState(); // Update state on check/uncheck
+                attrContainer.classList.toggle('disabled', !cb.checked); // Enable/Disable chips
+            });
+
+            // c) Row Click Listener (Updated to ignore button clicks)
+            tr.addEventListener('click', (e) => { 
+                const tag = e.target.tagName.toLowerCase();
+                if (tag !== 'input' && tag !== 'button') { 
+                    cb.checked = !cb.checked; 
+                    cb.dispatchEvent(new Event('change')); 
+                } 
+            });
+            
+            // 5. Append Rows to TBODY
+            tbody.appendChild(tr);
+            tbody.appendChild(detailRow); // Append the detail row right after the main row
+        });
+    } else {
+        const tr = el('tr'); 
+        const tdName = el('td');
+        tdName.colSpan = 7; 
+        tdName.innerHTML = "No Products Found.";
+        tr.appendChild(tdName);
+        tbody.appendChild(tr);
+    }
+
+    table.appendChild(tbody);
+    wrap.appendChild(table);
+}
 
 function renderInlineForCards() {
     const api = FAKE_API_RESPONSE;
@@ -350,31 +629,34 @@ function renderInlineForCards() {
     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.item_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);
+        const inline = document.querySelector(`.attr-inline[data-pid="${p.item_id}"]`);
+        if (!inline) return;
+        
+        // --- CHANGE HERE: Use the new helper function ---
+        if (!isProductSelected(p.item_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
@@ -382,10 +664,10 @@ function renderInlineForCards() {
     $('#statOk').textContent = api.successful ?? 0;
     $('#statKo').textContent = api.failed ?? 0;
     $('#api-summary').style.display = 'block';
-    
-    
 }
 
+// -----------------------------------------------------------
+
 function renderInlineForTable() {
     const api = FAKE_API_RESPONSE;
     const table = $('#tableContainer');
@@ -394,40 +676,44 @@ function renderInlineForTable() {
     table.querySelectorAll('tr.detail-row').forEach(r => r.remove());
 
     PRODUCT_BASE.forEach((p, idx) => {
-    if (!selectedIds.has(p.item_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);
+        // --- CHANGE HERE: Use the new helper function ---
+        if (!isProductSelected(p.item_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');
+        // NOTE: The table rendering uses p.id for the row ID: `row-${p.id}`.
+        // Assuming p.id is still valid for finding the base row, as your original code used it.
+        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
@@ -436,6 +722,7 @@ function renderInlineForTable() {
     $('#statKo').textContent = api.failed ?? 0;
 }
 
+
 function renderInlineAttributes() {
     if (layoutMode === 'cards') renderInlineForCards(); else renderInlineForTable();
 }
@@ -461,7 +748,12 @@ function renderProducts() {
 
 // --- Submit & Reset ---
 function submitAttributes() {
-    if (selectedIds.size === 0) { alert('Please select at least one product.'); return; }
+  // Check the length of the new array
+    if (selectedProductsWithAttributes.length === 0) { 
+        alert('Please select at least one product.'); 
+        return; 
+    } 
+    // if (selectedIds.size === 0) { alert('Please select at least one product.'); return; }
     console.log("selectedIds",selectedIds);
     jQuery('#full-page-loader').show();  
     // let inputArray = {
@@ -470,13 +762,30 @@ function submitAttributes() {
     const extractAdditional = document.getElementById('extract_additional').checked;
     const processImage = document.getElementById('process_image').checked;
 
+    // Transform the new state array into the required API format
+    const itemIds = selectedProductsWithAttributes.map(p => p.item_id);
+    
+    // Create the mandatory_attrs map: { item_id: { attr_name: [values] } }
+    // NOTE: The backend API you showed expects a flattened list of "mandatory_attrs"
+    // like: { "color": ["color", "shade"], "size": ["size", "fit"] } 
+    // It seems to ignore the selected product-specific values and uses a general list of synonyms.
+    // Assuming the request needs a general map of *all unique* selected attributes across all selected products:
+    
+    let mandatoryAttrsMap = {};
+    selectedProductsWithAttributes.forEach(product => {
+        // Merge attributes from all selected products
+        Object.assign(mandatoryAttrsMap, product.mandatory_attrs);
+    });
+
+    // If the API expects the complex, product-specific payload from your Q1 example:
+    const payloadForQ1 = selectedProductsWithAttributes.map(p => ({
+        item_id: p.item_id,
+        mandatory_attrs: p.mandatory_attrs
+    }));
+
     let inputArray = {
-              "item_ids": [...selectedIds],
-              "mandatory_attrs": {
-                "color": ["color", "shade"],
-                "size": ["size", "fit"],
-                "fabric": ["fabric", "material"]
-              },
+              "products": payloadForQ1,
+              
               "model": "llama-3.1-8b-instant",
               "extract_additional": extractAdditional,
               "process_image": processImage
@@ -500,7 +809,8 @@ function submitAttributes() {
 }
 
 function resetAll() {
-    selectedIds.clear();
+  selectedProductsWithAttributes = []; // Reset the main array
+    // selectedIds.clear();
     lastSeen.clear();
     renderProducts();
     // Clear summary
@@ -685,4 +995,50 @@ function renderPagination() {
   }
 }
 
+// Function to add/remove product from the state and manage its attributes
+function toggleProductSelection(itemId, isChecked, attributes = {}) {
+    const index = selectedProductsWithAttributes.findIndex(p => p.item_id === itemId);
+
+    if (isChecked) {
+        // If selecting, ensure the product object exists in the array
+        if (index === -1) {
+            selectedProductsWithAttributes.push({
+                item_id: itemId,
+                mandatory_attrs: attributes
+            });
+        } else {
+            // Update attributes if the product is already selected
+            selectedProductsWithAttributes[index].mandatory_attrs = attributes;
+        }
+    } else {
+        // If deselecting, remove the product object from the array
+        if (index !== -1) {
+            selectedProductsWithAttributes.splice(index, 1);
+        }
+    }
+    updateSelectionInfo();
+}
+
+// Function to get the current mandatory attributes for a selected item
+function getSelectedAttributes(itemId) {
+    const productEntry = selectedProductsWithAttributes.find(p => p.item_id === itemId);
+    return productEntry ? productEntry.mandatory_attrs : {};
+}
+
+// Helper to check if a product is selected
+function isProductSelected(itemId) {
+    return selectedProductsWithAttributes.some(p => p.item_id === itemId);
+}
+
+// Helper to check if a specific attribute/value is selected
+function isAttributeValueSelected(itemId, attrName, value) {
+    const attrs = getSelectedAttributes(itemId);
+    const values = attrs[attrName];
+    return values ? values.includes(value) : false; // Default all selected when first loaded
+}
+
+
 
+$('.attribute-select').select2({
+    placeholder: 'Select product attributes'
+});

+ 99 - 6
content_quality_tool_public/templates/product-attributes.html

@@ -125,8 +125,8 @@
                              <div class="card-header">
                                 <div class="d-flex justify-content-between align-items-center mb-3">
                                     <h3>Manage Attributes</h3>
-                                    <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addProductTypeModal">Add New Product Type</button>
-                                    <button class="btn btn-info" data-bs-toggle="modal" data-bs-target="#addProductAttributeModal">Add New Attribute</button>
+                                    <!-- <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addProductTypeModal">Add New Product Type</button>
+                                    <button class="btn btn-info" data-bs-toggle="modal" data-bs-target="#addProductAttributeModal">Add New Attribute</button> -->
                                 </div>
                             </div>
                             <div class="card-body">
@@ -136,11 +136,11 @@
                                         <th>ID</th>
                                         <th>Product Type</th>
                                         <th>Product Attribute List</th>
-                                        <th>Actions</th>
+                                        <!-- <th>Actions</th> -->
                                     </tr>
                                     </thead>
                                     <tbody id="dataBody">
-                                    <tr>
+                                    <!-- <tr>
                                         <td>1</td>
                                         <td>Canvas Art Prints</td>
                                         <td><ul>
@@ -151,7 +151,7 @@
                                         <td>
                                         <button class="btn btn-sm btn-warning" data-bs-toggle="modal"  data-bs-target="#editModal">Edit</button>
                                         </td>
-                                    </tr>
+                                    </tr> -->
                                     </tbody>
                                 </table>
                             </div>
@@ -281,7 +281,7 @@
             }
         });
         $(document).ready(function () {
-            $('#product-attribute-list').DataTable();
+            // $('#product-attribute-list').DataTable();
         });
     </script> <!--end::OverlayScrollbars Configure--> <!-- OPTIONAL SCRIPTS --> <!-- apexcharts -->
     
@@ -324,6 +324,9 @@ $(document).ready(function () {
             placeholder: 'Select product attributes'
         });
     });
+
+    var API_DATA = []
+    
     
     document.addEventListener('DOMContentLoaded', function () {
         const form = document.getElementById('uploadForm');
@@ -382,6 +385,96 @@ $(document).ready(function () {
             });
         });
     });
+    document.addEventListener('DOMContentLoaded', () => {
+        jQuery('#full-page-loader').show();  
+            try{
+            fetch('/attr/products/attributes', {
+                method: 'GET', // or 'POST' if your API expects POST
+                headers: {
+                    'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || ''
+                }
+            })
+            .then(response => response.json())
+            .then(data => {
+                console.log("data",data);
+                API_DATA = data;
+                renderAttributeTable(API_DATA);
+                // 2. ONLY THEN, initialize DataTables
+                if (jQuery.fn.DataTable) { // Optional safety check
+                    jQuery('#product-attribute-list').DataTable({
+                        // Ensure the table structure is maintained if you re-render
+                        destroy: true 
+                    });
+                } else {
+                    console.error("DataTables plugin not loaded.");
+                }
+                jQuery('#full-page-loader').hide();
+            });
+        }catch(err){
+            console.log("err",err);
+            jQuery('#full-page-loader').hide();
+
+        }
+    });
+
+    // Function to generate the data and populate the table
+function renderAttributeTable(apiData) {
+    const $dataBody = jQuery('#dataBody');
+    $dataBody.empty(); // Clear existing rows
+
+    if (!apiData || apiData.length === 0) {
+        $dataBody.append('<tr><td colspan="4">No product attributes found.</td></tr>');
+        return;
+    }
+
+    // 1. Iterate directly over the flat API data
+    apiData.forEach((item, index) => {
+        // Prepare the content for the third column (Attribute List)
+        const attributeName = item.attribute_name;
+        const isMandatory = item.is_mandatory;
+        const possibleValues = item.possible_values;
+        const productType = item.product_type;
+        
+        // --- Column 3: Product Attribute List (UL/LI) ---
+        const $attributeList = jQuery('<ul></ul>');
+        
+        let className = isMandatory === 'Yes' ? 'mandatory-attr' : 'optional-attr';
+        let status = isMandatory === 'Yes' ? ' (Mandatory)' : ' (Optional)';
+
+        // Create a single <li> for this attribute
+        const liContent = `<strong>${attributeName}</strong>${status}: ${possibleValues}`;
+        const $listItem = jQuery(`<li class="${className}"></li>`).html(liContent);
+        $attributeList.append($listItem);
+
+        // --- Create the Table Row ---
+        const $newRow = jQuery('<tr></tr>');
+
+        // Column 1: ID (Using index + 1 for simple row numbering)
+        $newRow.append(`<td>${index + 1}</td>`);
+
+        // Column 2: Product Type
+        $newRow.append(`<td>${productType}</td>`);
+
+        // Column 3: Product Attribute List
+        // Note: Each row now contains only one attribute name in the list
+        const $listCell = jQuery('<td></td>').append($attributeList);
+        $newRow.append($listCell);
+
+        // Column 4: Actions (Edit Button)
+        const $actionsCell = jQuery('<td></td>');
+        // const $editButton = jQuery('<button class="btn btn-sm btn-warning" data-bs-toggle="modal" data-bs-target="#editModal">Edit</button>');
+        
+        // Attach data specific to this attribute for modal editing
+        // $editButton.data('attributeData', item); 
+
+        // $actionsCell.append($editButton);
+        // $newRow.append($actionsCell);
+
+        // Append the row to the table body
+        $dataBody.append($newRow);
+    });
+    
+}
 </script>
 </html>