|
|
@@ -10,6 +10,7 @@ const ACCEPT_TYPES = '*'; // e.g., 'image/*,.csv,.xlsx'
|
|
|
const thresholdInput = document.getElementById('thresholdRange');
|
|
|
const thresholdValueDisplay = document.getElementById('thresholdValue');
|
|
|
|
|
|
+var attributesFullData = [];
|
|
|
|
|
|
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' },
|
|
|
@@ -1316,35 +1317,49 @@ function renderInlineForCards() {
|
|
|
* @param {string} title - The title for the table section.
|
|
|
* @returns {HTMLElement} A div containing the comparison table.
|
|
|
*/
|
|
|
-function renderMandatoryComparisonTable(attributes, title) {
|
|
|
+function renderMandatoryComparisonTable(attributes, title, productType) {
|
|
|
const section = el('div', 'attribute-section');
|
|
|
-
|
|
|
+
|
|
|
let attributeEntries = [];
|
|
|
|
|
|
+ // --- Build attributeEntries ---
|
|
|
Object.keys(attributes).forEach(key => {
|
|
|
- const valuesArray = Array.isArray(attributes[key]) ? attributes[key] : [attributes[key]];
|
|
|
+ const valuesArray = Array.isArray(attributes[key]) ? attributes[key] : [attributes[key]];
|
|
|
valuesArray.forEach(v => {
|
|
|
const aiValue = v.value || 'N/A';
|
|
|
const originalValue = v.original_value || 'N/A';
|
|
|
const source = v.source || 'N/A';
|
|
|
+ const reason = v.reason || 'N/A';
|
|
|
+
|
|
|
+ // Find possible values for this attribute from full data
|
|
|
+ const attrConfig = attributesFullData.find(item => item.attribute_name === key);
|
|
|
|
|
|
- // Comparison is case-insensitive and ignores leading/trailing whitespace
|
|
|
- const isMatch = (String(aiValue).trim().toLowerCase() === String(originalValue).trim().toLowerCase());
|
|
|
+ let possibleValues = [];
|
|
|
+ if (attrConfig && attrConfig.possible_values) {
|
|
|
+ possibleValues = attrConfig.possible_values.split(',').map(s => s.trim());
|
|
|
+ }
|
|
|
+
|
|
|
+ // Determine match flag
|
|
|
+ const isFoundInPossible = possibleValues.includes(aiValue);
|
|
|
+ const matchFlag = isFoundInPossible ? true : false;
|
|
|
|
|
|
attributeEntries.push({
|
|
|
name: key,
|
|
|
aiValue: aiValue,
|
|
|
+ possibleValues: possibleValues,
|
|
|
originalValue: originalValue,
|
|
|
source: source,
|
|
|
- isMatch: isMatch
|
|
|
+ isMatch: matchFlag,
|
|
|
+ reason: reason
|
|
|
});
|
|
|
});
|
|
|
});
|
|
|
|
|
|
+ // --- Section title ---
|
|
|
const titleEl = el('div', 'section-title');
|
|
|
titleEl.innerHTML = `<h3>${title} (${attributeEntries.length})</h3>`;
|
|
|
section.appendChild(titleEl);
|
|
|
-
|
|
|
+
|
|
|
if (attributeEntries.length === 0) {
|
|
|
const msg = el('p', 'no-attributes-message');
|
|
|
msg.textContent = `No ${title.toLowerCase()} found.`;
|
|
|
@@ -1352,63 +1367,340 @@ function renderMandatoryComparisonTable(attributes, title) {
|
|
|
return section;
|
|
|
}
|
|
|
|
|
|
- // --- SCROLL WRAPPER ADDITION ---
|
|
|
- const scrollWrapper = el('div', 'attribute-scroll-wrapper');
|
|
|
- const table = el('table', 'attribute-detail-table comparison-table');
|
|
|
+ // --- Split attributes in half ---
|
|
|
+ const midIndex = Math.ceil(attributeEntries.length / 2);
|
|
|
+ const leftAttributes = attributeEntries.slice(0, midIndex);
|
|
|
+ const rightAttributes = attributeEntries.slice(midIndex);
|
|
|
+
|
|
|
+ // --- Create a container for two tables ---
|
|
|
+ const tableContainer = el('div', 'two-column-table-container');
|
|
|
+
|
|
|
+ // Helper function to build a table
|
|
|
+ function buildTable(attrArray) {
|
|
|
+ const scrollWrapper = el('div', 'attribute-scroll-wrapper');
|
|
|
+ const table = el('table', 'attribute-detail-table comparison-table');
|
|
|
+
|
|
|
+ const thead = el('thead');
|
|
|
+ const headerRow = el('tr');
|
|
|
+ ['Attribute Name', 'AI Generated Value', 'Action'].forEach(text => {
|
|
|
+ const th = el('th');
|
|
|
+ th.textContent = text;
|
|
|
+ headerRow.appendChild(th);
|
|
|
+ });
|
|
|
+ thead.appendChild(headerRow);
|
|
|
+ table.appendChild(thead);
|
|
|
+
|
|
|
+ const tbody = el('tbody');
|
|
|
+
|
|
|
+ attrArray.forEach(attr => {
|
|
|
+ const row = el('tr', attr.isMatch ? 'match-row' : 'mismatch-row');
|
|
|
+
|
|
|
+ // Attribute name
|
|
|
+ const nameTd = el('td', 'attribute-name');
|
|
|
+ nameTd.textContent = formatString(attr.name).replace(/_/g, ' ');
|
|
|
+ row.appendChild(nameTd);
|
|
|
+
|
|
|
+ // AI Value display
|
|
|
+ // AI Value cell with info icon
|
|
|
+ const aiValueTd = el('td', `ai-value ${attr.isMatch ? '' : 'mismatch-value'}`);
|
|
|
+
|
|
|
+ // Create a wrapper for text and icon
|
|
|
+ const aiValueWrapper = document.createElement('div');
|
|
|
+ aiValueWrapper.style.display = 'flex';
|
|
|
+ aiValueWrapper.style.alignItems = 'center';
|
|
|
+ aiValueWrapper.style.gap = '6px';
|
|
|
+
|
|
|
+ // Text node
|
|
|
+ const valueText = document.createElement('span');
|
|
|
+ valueText.textContent = formatString(attr.aiValue);
|
|
|
+ aiValueWrapper.appendChild(valueText);
|
|
|
+
|
|
|
+ // Info icon (Bootstrap Icons)
|
|
|
+ if (attr.reason && attr.reason.trim() !== '') {
|
|
|
+ const infoIcon = document.createElement('i');
|
|
|
+ infoIcon.className = 'bi bi-info-circle';
|
|
|
+ infoIcon.style.cursor = 'pointer';
|
|
|
+ infoIcon.setAttribute('title', attr.reason); // Tooltip on hover
|
|
|
+ infoIcon.style.color = '#0d6efd'; // Bootstrap blue
|
|
|
+ aiValueWrapper.appendChild(infoIcon);
|
|
|
+ }
|
|
|
+
|
|
|
+ aiValueTd.appendChild(aiValueWrapper);
|
|
|
+ row.appendChild(aiValueTd);
|
|
|
+
|
|
|
+ // const aiValueTd = el('td', `ai-value ${attr.isMatch ? '' : 'mismatch-value'}`);
|
|
|
+ // aiValueTd.textContent = formatString(attr.aiValue);
|
|
|
+ // row.appendChild(aiValueTd);
|
|
|
+
|
|
|
+ // Dropdown
|
|
|
+ // const aiTd = el('td', 'attribute-source');
|
|
|
+ // const select = document.createElement('select');
|
|
|
+ // select.classList.add('select2-dropdown');
|
|
|
+ // select.setAttribute('data-attribute', attr.name);
|
|
|
+
|
|
|
+ const aiTd = el('td', 'attribute-source');
|
|
|
+
|
|
|
+ // Create a multi-select dropdown
|
|
|
+ const select = document.createElement('select');
|
|
|
+ select.classList.add('select2-dropdown');
|
|
|
+ // select.id = 'manually-attributes'; // You may want to make this unique per row
|
|
|
+ select.name = 'manuallyUpdatedAttributes[]';
|
|
|
+ // select.setAttribute('aria-labelledby', 'select a attribute for which multiple data required');
|
|
|
+ select.setAttribute('multiple', 'multiple');
|
|
|
+ // select.style.width = '100%';
|
|
|
+ select.setAttribute('data-attribute', attr.name);
|
|
|
+
|
|
|
+
|
|
|
+ // Populate options
|
|
|
+ attr.possibleValues.forEach(val => {
|
|
|
+ const option = document.createElement('option');
|
|
|
+ option.value = val;
|
|
|
+ option.textContent = val;
|
|
|
+ if (val === attr.aiValue) option.selected = true;
|
|
|
+ select.appendChild(option);
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!attr.isMatch && attr.aiValue !== 'N/A') {
|
|
|
+ const newOpt = document.createElement('option');
|
|
|
+ newOpt.value = attr.aiValue;
|
|
|
+ newOpt.textContent = attr.aiValue + " (new)";
|
|
|
+ newOpt.selected = true;
|
|
|
+ select.appendChild(newOpt);
|
|
|
+ }
|
|
|
+
|
|
|
+ aiTd.appendChild(select);
|
|
|
+ row.appendChild(aiTd);
|
|
|
+
|
|
|
+ tbody.appendChild(row);
|
|
|
+
|
|
|
+ // Initialize Select2
|
|
|
+ jQuery(select).select2({
|
|
|
+ tags: true,
|
|
|
+ width: 'resolve'
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ table.appendChild(tbody);
|
|
|
+ scrollWrapper.appendChild(table);
|
|
|
+ return scrollWrapper;
|
|
|
+ }
|
|
|
+
|
|
|
+ // --- Build and append both tables ---
|
|
|
+ const leftTable = buildTable(leftAttributes);
|
|
|
+ const rightTable = buildTable(rightAttributes);
|
|
|
+
|
|
|
+ tableContainer.appendChild(leftTable);
|
|
|
+ tableContainer.appendChild(rightTable);
|
|
|
+
|
|
|
+ section.appendChild(tableContainer);
|
|
|
+ return section;
|
|
|
+}
|
|
|
+
|
|
|
+// function renderMandatoryComparisonTable(attributes, title, productType) {
|
|
|
+// const section = el('div', 'attribute-section');
|
|
|
+
|
|
|
+// let attributeEntries = [];
|
|
|
+
|
|
|
+// Object.keys(attributes).forEach(key => {
|
|
|
+// const valuesArray = Array.isArray(attributes[key]) ? attributes[key] : [attributes[key]];
|
|
|
+// valuesArray.forEach(v => {
|
|
|
+// const aiValue = v.value || 'N/A';
|
|
|
+// const originalValue = v.original_value || 'N/A';
|
|
|
+// const source = v.source || 'N/A';
|
|
|
+
|
|
|
+// // Find possible values for this attribute from `a`
|
|
|
+// const attrConfig = attributesFullData.find(item =>
|
|
|
+// item.attribute_name === key
|
|
|
+// );
|
|
|
+
|
|
|
+// let possibleValues = [];
|
|
|
+// if (attrConfig && attrConfig.possible_values) {
|
|
|
+// possibleValues = attrConfig.possible_values.split(',').map(s => s.trim());
|
|
|
+// }
|
|
|
+
|
|
|
+// // Determine if AI value exists in possible values
|
|
|
+// const isFoundInPossible = possibleValues.includes(aiValue);
|
|
|
+// const matchFlag = isFoundInPossible ? true : false;
|
|
|
+
|
|
|
+// attributeEntries.push({
|
|
|
+// name: key,
|
|
|
+// aiValue: aiValue,
|
|
|
+// possibleValues: possibleValues,
|
|
|
+// originalValue: originalValue,
|
|
|
+// source: source,
|
|
|
+// isMatch: matchFlag
|
|
|
+// });
|
|
|
+// });
|
|
|
+// });
|
|
|
+
|
|
|
+// const titleEl = el('div', 'section-title');
|
|
|
+// titleEl.innerHTML = `<h3>${title} (${attributeEntries.length})</h3>`;
|
|
|
+// section.appendChild(titleEl);
|
|
|
+
|
|
|
+// if (attributeEntries.length === 0) {
|
|
|
+// const msg = el('p', 'no-attributes-message');
|
|
|
+// msg.textContent = `No ${title.toLowerCase()} found.`;
|
|
|
+// section.appendChild(msg);
|
|
|
+// return section;
|
|
|
+// }
|
|
|
+
|
|
|
+// const scrollWrapper = el('div', 'attribute-scroll-wrapper');
|
|
|
+// const table = el('table', 'attribute-detail-table comparison-table');
|
|
|
+
|
|
|
+// const thead = el('thead');
|
|
|
+// const headerRow = el('tr');
|
|
|
+
|
|
|
+// ['Attribute Name', 'AI Generated Value', 'Action'].forEach(text => {
|
|
|
+// const th = el('th');
|
|
|
+// th.textContent = text;
|
|
|
+// headerRow.appendChild(th);
|
|
|
+// });
|
|
|
+// thead.appendChild(headerRow);
|
|
|
+// table.appendChild(thead);
|
|
|
+
|
|
|
+// const tbody = el('tbody');
|
|
|
+
|
|
|
+// attributeEntries.forEach(attr => {
|
|
|
+// const row = el('tr', attr.isMatch ? 'match-row' : 'mismatch-row');
|
|
|
+
|
|
|
+// // 1. Attribute Name
|
|
|
+// const nameTd = el('td', 'attribute-name');
|
|
|
+// nameTd.textContent = formatString(attr.name).replace(/_/g, ' ');
|
|
|
+// row.appendChild(nameTd);
|
|
|
+
|
|
|
+// // 3. Source Column
|
|
|
+// const sourceTd = el('td', `ai-value ${attr.isMatch ? '' : 'mismatch-value'}`);
|
|
|
+// sourceTd.textContent = formatString(attr.aiValue);
|
|
|
+// row.appendChild(sourceTd);
|
|
|
+
|
|
|
+
|
|
|
+// // 2. AI Value Dropdown (Select2)
|
|
|
+// const aiTd = el('td', 'attribute-source');
|
|
|
+
|
|
|
+// const select = document.createElement('select');
|
|
|
+// select.classList.add('select2-dropdown');
|
|
|
+// select.setAttribute('data-attribute', attr.name);
|
|
|
+
|
|
|
+// // Populate possible values
|
|
|
+// attr.possibleValues.forEach(val => {
|
|
|
+// const option = document.createElement('option');
|
|
|
+// option.value = val;
|
|
|
+// option.textContent = val;
|
|
|
+// if (val === attr.aiValue) option.selected = true;
|
|
|
+// select.appendChild(option);
|
|
|
+// });
|
|
|
+
|
|
|
+// // If not found in possible values, add it as a new option
|
|
|
+// if (!attr.isMatch && attr.aiValue !== 'N/A') {
|
|
|
+// const newOpt = document.createElement('option');
|
|
|
+// newOpt.value = attr.aiValue;
|
|
|
+// newOpt.textContent = attr.aiValue + " (new)";
|
|
|
+// newOpt.selected = true;
|
|
|
+// select.appendChild(newOpt);
|
|
|
+// }
|
|
|
+
|
|
|
+// aiTd.appendChild(select);
|
|
|
+// row.appendChild(aiTd);
|
|
|
+
|
|
|
+
|
|
|
+// tbody.appendChild(row);
|
|
|
+
|
|
|
+// // Initialize Select2 after element is added
|
|
|
+// jQuery(select).select2({
|
|
|
+// tags: true, // allows new values
|
|
|
+// width: 'resolve'
|
|
|
+// });
|
|
|
+// });
|
|
|
+
|
|
|
+// table.appendChild(tbody);
|
|
|
+// scrollWrapper.appendChild(table);
|
|
|
+// section.appendChild(scrollWrapper);
|
|
|
+
|
|
|
+// return section;
|
|
|
+// }
|
|
|
+
|
|
|
+// function renderMandatoryComparisonTable(attributes, title) {
|
|
|
+// const section = el('div', 'attribute-section');
|
|
|
|
|
|
- const thead = el('thead');
|
|
|
- const headerRow = el('tr');
|
|
|
+// let attributeEntries = [];
|
|
|
+
|
|
|
+// Object.keys(attributes).forEach(key => {
|
|
|
+// const valuesArray = Array.isArray(attributes[key]) ? attributes[key] : [attributes[key]];
|
|
|
+// valuesArray.forEach(v => {
|
|
|
+// const aiValue = v.value || 'N/A';
|
|
|
+// const originalValue = v.original_value || 'N/A';
|
|
|
+// const source = v.source || 'N/A';
|
|
|
+
|
|
|
+// // Comparison is case-insensitive and ignores leading/trailing whitespace
|
|
|
+// const isMatch = (String(aiValue).trim().toLowerCase() === String(originalValue).trim().toLowerCase());
|
|
|
+
|
|
|
+// attributeEntries.push({
|
|
|
+// name: key,
|
|
|
+// aiValue: aiValue,
|
|
|
+// originalValue: originalValue,
|
|
|
+// source: source,
|
|
|
+// isMatch: isMatch
|
|
|
+// });
|
|
|
+// });
|
|
|
+// });
|
|
|
+
|
|
|
+// const titleEl = el('div', 'section-title');
|
|
|
+// titleEl.innerHTML = `<h3>${title} (${attributeEntries.length})</h3>`;
|
|
|
+// section.appendChild(titleEl);
|
|
|
|
|
|
- // Updated Headers for Comparison Table
|
|
|
- ['Attribute Name', 'Manually Identified Value', 'AI Generated Value', 'Source', 'Match'].forEach(text => {
|
|
|
- const th = el('th');
|
|
|
- th.textContent = text;
|
|
|
- headerRow.appendChild(th);
|
|
|
- });
|
|
|
- thead.appendChild(headerRow);
|
|
|
- table.appendChild(thead);
|
|
|
+// if (attributeEntries.length === 0) {
|
|
|
+// const msg = el('p', 'no-attributes-message');
|
|
|
+// msg.textContent = `No ${title.toLowerCase()} found.`;
|
|
|
+// section.appendChild(msg);
|
|
|
+// return section;
|
|
|
+// }
|
|
|
|
|
|
- const tbody = el('tbody');
|
|
|
+// // --- SCROLL WRAPPER ADDITION ---
|
|
|
+// const scrollWrapper = el('div', 'attribute-scroll-wrapper');
|
|
|
+// const table = el('table', 'attribute-detail-table comparison-table');
|
|
|
+
|
|
|
+// const thead = el('thead');
|
|
|
+// const headerRow = el('tr');
|
|
|
+
|
|
|
+// // Updated Headers for Comparison Table
|
|
|
+// ['Attribute Name', 'AI Generated Value', 'Action'].forEach(text => {
|
|
|
+// const th = el('th');
|
|
|
+// th.textContent = text;
|
|
|
+// headerRow.appendChild(th);
|
|
|
+// });
|
|
|
+// thead.appendChild(headerRow);
|
|
|
+// table.appendChild(thead);
|
|
|
|
|
|
- attributeEntries.forEach(attr => {
|
|
|
- // Highlight the entire row in red if the values do not match
|
|
|
- const row = el('tr', attr.isMatch ? 'match-row' : 'mismatch-row');
|
|
|
+// const tbody = el('tbody');
|
|
|
+
|
|
|
+// attributeEntries.forEach(attr => {
|
|
|
+// // Highlight the entire row in red if the values do not match
|
|
|
+// const row = el('tr', attr.isMatch ? 'match-row' : 'mismatch-row');
|
|
|
|
|
|
- // 1. Attribute Name
|
|
|
- const nameTd = el('td', 'attribute-name');
|
|
|
- nameTd.textContent = formatString(attr.name).replace(/_/g, ' ');
|
|
|
- row.appendChild(nameTd);
|
|
|
+// // 1. Attribute Name
|
|
|
+// const nameTd = el('td', 'attribute-name');
|
|
|
+// nameTd.textContent = formatString(attr.name).replace(/_/g, ' ');
|
|
|
+// row.appendChild(nameTd);
|
|
|
|
|
|
- // 2. Existing Value (Manually added value)
|
|
|
- const originalTd = el('td', 'original-value');
|
|
|
- originalTd.textContent = formatString(attr.originalValue);
|
|
|
- row.appendChild(originalTd);
|
|
|
|
|
|
- // 3. AI Extracted Value
|
|
|
- const aiTd = el('td', `ai-value ${attr.isMatch ? '' : 'mismatch-value'}`);
|
|
|
- aiTd.textContent = attr.aiValue;
|
|
|
- row.appendChild(aiTd);
|
|
|
+// // 3. AI Extracted Value
|
|
|
+// const aiTd = el('td', `ai-value ${attr.isMatch ? '' : 'mismatch-value'}`);
|
|
|
+// aiTd.textContent = attr.aiValue;
|
|
|
+// row.appendChild(aiTd);
|
|
|
|
|
|
- // 4. Source
|
|
|
- const sourceTd = el('td', 'attribute-source');
|
|
|
- sourceTd.textContent = formatString(attr.source);
|
|
|
- row.appendChild(sourceTd);
|
|
|
-
|
|
|
- // 5. Match Status
|
|
|
- const matchTd = el('td', 'match-status');
|
|
|
- const matchPill = el('span', `pill status-pill status-${attr.isMatch ? 'match' : 'mismatch'}`);
|
|
|
- matchPill.textContent = attr.isMatch ? '✅ MATCH' : '❌ MISMATCH';
|
|
|
- matchTd.appendChild(matchPill);
|
|
|
- row.appendChild(matchTd);
|
|
|
+// // 4. Source
|
|
|
+// const sourceTd = el('td', 'attribute-source');
|
|
|
+// sourceTd.textContent = formatString(attr.source);
|
|
|
+// row.appendChild(sourceTd);
|
|
|
|
|
|
- tbody.appendChild(row);
|
|
|
- });
|
|
|
+// tbody.appendChild(row);
|
|
|
+// });
|
|
|
|
|
|
- table.appendChild(tbody);
|
|
|
- scrollWrapper.appendChild(table); // Append table to wrapper
|
|
|
- section.appendChild(scrollWrapper); // Append wrapper to section
|
|
|
- return section;
|
|
|
-}
|
|
|
+// table.appendChild(tbody);
|
|
|
+// scrollWrapper.appendChild(table); // Append table to wrapper
|
|
|
+// section.appendChild(scrollWrapper); // Append wrapper to section
|
|
|
+// return section;
|
|
|
+// }
|
|
|
|
|
|
/**
|
|
|
* Renders a table for Additional, OCR, or Visual attributes (Name, Value, Source).
|
|
|
@@ -2205,6 +2497,7 @@ function getAtributeList(){
|
|
|
.then(response => response.json())
|
|
|
.then(data => {
|
|
|
// console.log("data",data);
|
|
|
+ attributesFullData = data;
|
|
|
let attributesData = data;
|
|
|
// Step 1: Extract unique mandatory attribute names
|
|
|
const mandatoryAttributes = [...new Set(
|