Browse Source

Merge branch 'master' of https://git.luminad.com/harshit.pathak/content_quality_tool

Student Yadav 3 months ago
parent
commit
83eb6c5fd3

+ 251 - 101
content_quality_tool_public/static/js/attr-extraction.js

@@ -715,152 +715,302 @@ function createAttributeChips(p, attr, initialSelected, isMandatory, updateCallb
     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','table-responsive');
+    
+//     const thead = document.createElement('thead'); 
+//     const trh = document.createElement('tr');
+    
+//     // Table Headers
+//     ['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}`;
+//             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);
+//             // console.log("checkkkkk")
+            
+//             // 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.innerHTML = 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 = 6; // 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);
+
+//             if(p.product_type_details.length > 0){
+//                 // 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', () => { 
+//                 // console.log("cheeeeeeeeee");
+//                 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();
+//                 // console.log("clikk")
+//                 if (tag !== 'input' && tag !== 'button') { 
+//                     cb.checked = !cb.checked; 
+//                     cb.dispatchEvent(new Event('change')); 
+//                 } 
+//             });
+//             }else{
+//                 const tdAttr = document.createElement('td'); 
+//                 tr.appendChild(tdAttr);
+//                 // b) Main Checkbox Change Logic
+//                 cb.addEventListener('change', () => { 
+//                     // console.log("cheeeeeeeeee");
+//                     updateProductState(); // Update state on check/uncheck
+//                     attrContainer.classList.toggle('disabled', !cb.checked); // Enable/Disable chips
+//                 });
+//                 tr.addEventListener('click', (e) => { 
+//                     const tag = e.target.tagName.toLowerCase();
+//                     // console.log("clikk")
+//                     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 = 6; 
+//         tdName.innerHTML = "No Products Found.";
+//         tr.appendChild(tdName);
+//         tbody.appendChild(tr);
+//     }
+
+//     table.appendChild(tbody);
+//     wrap.appendChild(table);
+// }
+
 function renderProductsTable(items = getCurrentSlice()) {
     const wrap = document.getElementById('tableContainer');
     wrap.innerHTML = '';
     const table = document.createElement('table');
-    table.classList.add('table', 'table-striped', 'table-bordered','table-responsive');
-    
-    const thead = document.createElement('thead'); 
+    table.classList.add('table', 'table-striped', 'table-bordered', 'table-responsive');
+    table.id = 'productsTable'; // ✅ Add a unique table ID
+
+
+    const thead = document.createElement('thead');
     const trh = document.createElement('tr');
-    
-    // Table Headers
-    ['Select', 'Image', 'Product', 'SKU', 'Type', 'Short Description'].forEach(h => {
-        const th = document.createElement('th'); th.textContent = h; trh.appendChild(th);
+
+    // --- "Select All" Header with Checkbox ---
+    const thSelect = document.createElement('th');
+    const selectAllCheckbox = document.createElement('input');
+    selectAllCheckbox.type = 'checkbox';
+    selectAllCheckbox.id = 'selectAllCheckbox';
+    thSelect.appendChild(selectAllCheckbox);
+    trh.appendChild(thSelect);
+
+    // Other headers
+    ['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);
-    
+    thead.appendChild(trh);
+    table.appendChild(thead);
+
     const tbody = document.createElement('tbody');
-    
+
     if (items.length > 0) {
         items.forEach(p => {
-            const tr = document.createElement('tr'); 
+            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'; 
+            // Checkbox for each row
+            const cb = document.createElement('input');
+            cb.type = 'checkbox';
+            cb.classList.add('checkbox-productlist'); // ✅ correct way to add class
             cb.checked = isProductSelected(p.item_id);
-            // console.log("checkkkkk")
-            
-            // The state updater function is bound to this specific row/checkbox
+
             const updateProductState = getProductStateUpdater(p, cb, tr);
 
-            // --- Select Cell ---
-            const tdSel = document.createElement('td'); 
+            const tdSel = document.createElement('td');
             tdSel.className = 'select-cell';
-            tdSel.appendChild(cb); 
+            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.innerHTML = p.product_short_description || ''; tr.appendChild(tdDesc);
+            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);
 
-            // ---------------------------------------------
-            // --- ATTRIBUTE SELECTION IMPLEMENTATION ---
-            // ---------------------------------------------
+            const tdSku = document.createElement('td');
+            tdSku.textContent = p.item_id || '—';
+            tr.appendChild(tdSku);
 
-            // 1. DETAIL ROW STRUCTURE
+            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.innerHTML = p.product_short_description || '';
+            tr.appendChild(tdDesc);
+
+            // Handle attribute rows (kept your same logic)
             const detailRow = document.createElement('tr');
-            detailRow.classList.add('attribute-detail-row'); // Custom class for styling
-            detailRow.style.display = 'none'; // Initially hidden
+            detailRow.classList.add('attribute-detail-row');
+            detailRow.style.display = 'none';
             detailRow.id = `detail-row-${p.id}`;
-            
+
             const detailCell = document.createElement('td');
-            detailCell.colSpan = 6; // Must span all columns
-            
+            detailCell.colSpan = 6;
+
             const attrContainer = document.createElement('div');
-            attrContainer.id = `attr-container-${p.item_id}`; // Unique ID for targeting by updateProductState
+            attrContainer.id = `attr-container-${p.item_id}`;
             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);
 
-            if(p.product_type_details.length > 0){
-                // 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', () => { 
-                // console.log("cheeeeeeeeee");
-                updateProductState(); // Update state on check/uncheck
-                attrContainer.classList.toggle('disabled', !cb.checked); // Enable/Disable chips
+            // Checkbox behavior
+            cb.addEventListener('change', () => {
+                updateProductState();
+                attrContainer.classList.toggle('disabled', !cb.checked);
             });
 
-            // c) Row Click Listener (Updated to ignore button clicks)
-            tr.addEventListener('click', (e) => { 
+            tr.addEventListener('click', (e) => {
                 const tag = e.target.tagName.toLowerCase();
-                // console.log("clikk")
-                if (tag !== 'input' && tag !== 'button') { 
-                    cb.checked = !cb.checked; 
-                    cb.dispatchEvent(new Event('change')); 
-                } 
+                if (tag !== 'input' && tag !== 'button') {
+                    cb.checked = !cb.checked;
+                    cb.dispatchEvent(new Event('change'));
+                }
             });
-            }else{
-                const tdAttr = document.createElement('td'); 
-                tr.appendChild(tdAttr);
-                // b) Main Checkbox Change Logic
-                cb.addEventListener('change', () => { 
-                    // console.log("cheeeeeeeeee");
-                    updateProductState(); // Update state on check/uncheck
-                    attrContainer.classList.toggle('disabled', !cb.checked); // Enable/Disable chips
-                });
-                tr.addEventListener('click', (e) => { 
-                    const tag = e.target.tagName.toLowerCase();
-                    // console.log("clikk")
-                    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
+            tbody.appendChild(detailRow);
         });
     } else {
-        const tr = el('tr'); 
-        const tdName = el('td');
-        tdName.colSpan = 6; 
-        tdName.innerHTML = "No Products Found.";
-        tr.appendChild(tdName);
+        const tr = document.createElement('tr');
+        const td = document.createElement('td');
+        td.colSpan = 6;
+        td.textContent = 'No Products Found.';
+        tr.appendChild(td);
         tbody.appendChild(tr);
     }
 
     table.appendChild(tbody);
     wrap.appendChild(table);
+
+    // --- Select All Checkbox Logic ---
+    selectAllCheckbox.addEventListener('change', () => {
+        // ✅ Only get product checkboxes inside *this* table
+        const allCheckboxes = table.querySelectorAll('tbody .checkbox-productlist');
+        console.log("allCheckboxes", allCheckboxes);
+
+        allCheckboxes.forEach(cb => {
+            cb.checked = selectAllCheckbox.checked;
+            cb.dispatchEvent(new Event('change'));
+        });
+    });
+
+    // --- Keep "Select All" synced with individual selections ---
+    tbody.addEventListener('change', (e) => {
+        if (e.target && e.target.classList.contains('checkbox-productlist')) {
+            // ✅ Again, limit scope to *this* table
+            const allCheckboxes = table.querySelectorAll('tbody .checkbox-productlist');
+            const allChecked = Array.from(allCheckboxes).every(cb => cb.checked);
+            const someChecked = Array.from(allCheckboxes).some(cb => cb.checked);
+
+            selectAllCheckbox.checked = allChecked;
+            selectAllCheckbox.indeterminate = !allChecked && someChecked;
+        }
+    });
+
+
 }
 
+
 // function renderInlineForCards() {
 //     const api = API_RESPONSE_AI;
 //     // Clear all inline sections first
@@ -1463,7 +1613,7 @@ function renderMandatoryComparisonTable(attributes, title, productType) {
             if (!attr.isMatch && attr.aiValue !== 'N/A') {
                 const newOpt = document.createElement('option');
                 newOpt.value = attr.aiValue;
-                newOpt.textContent = attr.aiValue + " (new)";
+                newOpt.textContent = attr.aiValue; // + " (new)";
                 newOpt.selected = true;
                 select.appendChild(newOpt);
             }

+ 39 - 5
content_quality_tool_public/templates/attr-extraction.html

@@ -18,6 +18,16 @@
   <link rel="stylesheet" href="{% static 'css/custom.css' %}">
   <link rel="stylesheet" href="{% static 'css/attr-extraction.css' %}">
   <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
+  <style>
+    .pagination-bar-inline{
+        display: flex;
+        flex-direction: row;
+        flex-wrap: nowrap;
+        align-content: flex-end;
+        align-items: flex-end;
+        justify-content: flex-start;
+    }
+  </style>
 
 
 </head>
@@ -89,7 +99,7 @@
                         <input type="checkbox" id="process_image" name="process_image"/> -->
                         <button id="btnSubmit" class="btn btn-primary" title="AI Suggested">View AI Suggested Attributes</button>
                         <!-- <button id="btnReset" class="btn btn-secondary">Reset</button> -->
-                        <button id="btnSelectAll" class="btn btn-primary">Select all</button>
+                        <button id="btnSelectAll" class="btn btn-primary" style="display: none;">Select all</button>
                         <!-- Upload trigger button -->
                         <button id="btnUpload" type="button"
                                 class="btn btn-info"
@@ -146,7 +156,7 @@
                         <h2>Products</h2>
                         <div class="toolbar">
                             <span class="pill" id="selectionInfo">No products selected</span>
-                            <div class="seg" role="tablist" aria-label="Layout toggle">
+                            <div class="seg" role="tablist" aria-label="Layout toggle" style="display: none;">
                                 <button id="btnCards" class="active" role="tab" aria-selected="true">Cards</button>
                                 <button id="btnTable" role="tab" aria-selected="false">Table</button>
                             </div>
@@ -166,10 +176,16 @@
                          <!-- table-wrap  -->
                         <div id="tableContainer" class="table-responsive" style="display:none" aria-live="polite"></div>
                         <!-- Pagination bar -->
-                        <div id="paginationBar" class="pagination-bar" aria-label="Pagination" style="display: none;"></div>
+                        <div class="pagination-bar-inline">
+                            <div id="paginationBar" class="pagination-bar" aria-label="Pagination" style="display: none;"></div>
+                            <button id="SubmitBtn" type="button" style="float: right;position: absolute;right: 15px;"
+                                    class="btn btn-info">
+                                    Submit
+                            </button>
+                        </div>
                         </div>
                     </section>
-
+    
                     
                     </div>
 
@@ -220,6 +236,17 @@
             <!-- attr -->
           </div>
       </div>
+      <!-- Toast Container (bottom-right corner) -->
+        <div class="toast-container position-fixed top-0 end-0 p-3">
+            <div id="successToast" class="toast align-items-center text-bg-success border-0" role="alert" aria-live="assertive" aria-atomic="true">
+            <div class="d-flex">
+                <div class="toast-body">
+                Attribute extracted successfully.
+                </div>
+                <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
+            </div>
+            </div>
+        </div>
     </main>
 
 
@@ -278,6 +305,13 @@
         // });
     </script> 
     <script src="{% static 'js/attr-extraction.js' %}"></script>
+    <script>
+        document.getElementById("SubmitBtn").addEventListener("click", function() {
+            const toastEl = document.getElementById('successToast');
+            const toast = new bootstrap.Toast(toastEl);
+            toast.show();
+        });
+    </script>
     
 </body>
-</html>
+</html>

BIN
core/__pycache__/urls.cpython-313.pyc


BIN
core/__pycache__/views.cpython-313.pyc


+ 3 - 0
core/urls.py

@@ -22,6 +22,7 @@ from core.views import (
     ExcelUploadView,
 )
 from .views import open_outlook_mail
+from .views import ProductTypeQualityMetricsView
 
 
 
@@ -37,5 +38,7 @@ urlpatterns = [
     
     path('api/upload-rules/', ExcelUploadView.as_view(), name='upload_rules'),
 
+    path('api/quality-metrics/', ProductTypeQualityMetricsView.as_view(), name='quality-metrics'),
+
     path('run-script/', open_outlook_mail, name='open_outlook_mail'),
 ]

+ 61 - 0
core/views.py

@@ -975,3 +975,64 @@ def open_outlook_mail(request):
 
     except Exception as e:
         return HttpResponse(f"<h3>Error:</h3><p>{str(e)}</p>", status=500)
+
+
+
+
+
+
+# views.py
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import status
+from django.db.models import Avg, Count
+from .models import Product, AttributeScore  # adjust the import path as needed
+
+class ProductTypeQualityMetricsView(APIView):
+    """
+    API endpoint to fetch product type quality metrics.
+    Supports optional ?product_type=<type> query param.
+    """
+
+    def get(self, request):
+        product_type_filter = request.query_params.get('product_type', None)
+        queryset = Product.objects.all()
+
+        if product_type_filter:
+            queryset = queryset.filter(product_type=product_type_filter)
+
+        scored = (
+            AttributeScore.objects.filter(product__in=queryset)
+            .annotate(
+                title_quality=Avg('details__title_quality'),
+                description_quality=Avg('details__description_quality'),
+                image_quality=Avg('details__image_score'),
+                attributes_quality=Avg('details__attributes'),
+            )
+            .values('product__product_type')
+            .annotate(
+                product_count=Count('product', distinct=True),
+                scored_product_count=Count('id'),
+                avg_overall_score=Avg('score'),
+                avg_title_quality=Avg('details__title_quality'),
+                avg_description_quality=Avg('details__description_quality'),
+                avg_image_quality=Avg('details__image_score'),
+                avg_attributes_quality=Avg('details__attributes'),
+            )
+        )
+
+        results = [
+            {
+                "product_type": item['product__product_type'],
+                "product_count": item['product_count'],
+                "scored_product_count": item['scored_product_count'],
+                "avg_overall_score": round(item['avg_overall_score'] or 0, 2),
+                "avg_title_quality_percent": round(item['avg_title_quality'] or 0, 2),
+                "avg_description_quality_percent": round(item['avg_description_quality'] or 0, 2),
+                "avg_image_quality_percent": round(item['avg_image_quality'] or 0, 2),
+                "avg_attributes_quality_percent": round(item['avg_attributes_quality'] or 0, 2),
+            }
+            for item in scored
+        ]
+
+        return Response(results, status=status.HTTP_200_OK)