|
|
@@ -226,19 +226,19 @@ function createProductCard(p) {
|
|
|
// 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>`;
|
|
|
+ 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 => {
|
|
|
+ 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}`;
|
|
|
+ checkbox.name = `${p.item_id}-${attr?.attribute_name}`;
|
|
|
|
|
|
// Set initial state
|
|
|
checkbox.checked = initialSelected.includes(value);
|
|
|
@@ -311,7 +311,7 @@ if(p.product_type_details.length > 0){
|
|
|
// attrContainer.appendChild(manTitle);
|
|
|
|
|
|
// mandatoryAttributes.forEach(attr => {
|
|
|
- // const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
|
|
|
+ // const initialSelected = getSelectedAttributes(p.item_id)[attr?.attribute_name] || attr?.possible_values;
|
|
|
// const chipGroup = createAttributeChips(p, attr, initialSelected, true, updateProductState);
|
|
|
// attrContainer.appendChild(chipGroup);
|
|
|
// });
|
|
|
@@ -326,7 +326,7 @@ if(p.product_type_details.length > 0){
|
|
|
attrContainer.appendChild(optTitle);
|
|
|
|
|
|
optionalAttributes.forEach(attr => {
|
|
|
- const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
|
|
|
+ const initialSelected = getSelectedAttributes(p.item_id)[attr?.attribute_name] || attr?.possible_values;
|
|
|
const chipGroup = createAttributeChips(p, attr, initialSelected, false, updateProductState);
|
|
|
attrContainer.appendChild(chipGroup);
|
|
|
});
|
|
|
@@ -642,7 +642,7 @@ function generateAttributeUI(p, updateProductState, attrContainer) {
|
|
|
attrContainer.appendChild(manTitle);
|
|
|
|
|
|
mandatoryAttributes.forEach(attr => {
|
|
|
- const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
|
|
|
+ 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);
|
|
|
@@ -659,7 +659,7 @@ function generateAttributeUI(p, updateProductState, attrContainer) {
|
|
|
attrContainer.appendChild(optTitle);
|
|
|
|
|
|
optionalAttributes.forEach(attr => {
|
|
|
- const initialSelected = getSelectedAttributes(p.item_id)[attr.attribute_name] || attr.possible_values;
|
|
|
+ const initialSelected = getSelectedAttributes(p.item_id)[attr?.attribute_name] || attr?.possible_values;
|
|
|
const chipGroup = createAttributeChips(p, attr, initialSelected, false, updateProductState);
|
|
|
attrContainer.appendChild(chipGroup);
|
|
|
});
|
|
|
@@ -678,15 +678,15 @@ function generateAttributeUI(p, updateProductState, attrContainer) {
|
|
|
*/
|
|
|
function createAttributeChips(p, attr, initialSelected, isMandatory, updateCallback) {
|
|
|
const wrapper = el('div', 'attribute-chip-group');
|
|
|
- wrapper.dataset.attrName = attr.attribute_name;
|
|
|
+ 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>`;
|
|
|
+ wrapper.innerHTML = `<p class="attribute-header">${attr?.attribute_name}${statusText}:</p>`;
|
|
|
|
|
|
const chipContainer = el('div', 'chips-container');
|
|
|
|
|
|
- attr.possible_values.forEach(value => {
|
|
|
+ attr?.possible_values.forEach(value => {
|
|
|
const chip = el('label', 'attribute-chip');
|
|
|
|
|
|
// Checkbox input is hidden, but drives the selection state
|
|
|
@@ -694,7 +694,7 @@ function createAttributeChips(p, attr, initialSelected, isMandatory, updateCallb
|
|
|
checkbox.type = 'checkbox';
|
|
|
checkbox.value = value;
|
|
|
// Ensure the name is unique per product/attribute group
|
|
|
- checkbox.name = `${p.item_id}-${attr.attribute_name}`;
|
|
|
+ checkbox.name = `${p.item_id}-${attr?.attribute_name}`;
|
|
|
|
|
|
// Set initial state
|
|
|
checkbox.checked = initialSelected.includes(value);
|
|
|
@@ -792,7 +792,7 @@ function createAttributeChips(p, attr, initialSelected, isMandatory, updateCallb
|
|
|
// const toggleButton = document.createElement('button');
|
|
|
// toggleButton.textContent = 'Configure';
|
|
|
// toggleButton.classList.add('btn', 'btn-sm', 'btn-info', 'attribute-toggle-btn');
|
|
|
-// tdAttr.appendChild(toggleButton);
|
|
|
+// tdattr?.appendChild(toggleButton);
|
|
|
// // tr.appendChild(tdAttr);
|
|
|
|
|
|
|
|
|
@@ -1460,34 +1460,34 @@ function renderInlineForCards() {
|
|
|
|
|
|
// attributeEntries.forEach(attr => {
|
|
|
// // Highlight the entire row in red if the values do not match
|
|
|
-// const row = el('tr', attr.isMatch ? 'match' : 'mismatch-row');
|
|
|
+// const row = el('tr', attr?.isMatch ? 'match' : 'mismatch-row');
|
|
|
|
|
|
// // 1. Attribute Name
|
|
|
// const nameTd = el('td', 'attribute-name');
|
|
|
-// nameTd.textContent = attr.name.replace(/_/g, ' ');
|
|
|
+// nameTd.textContent = attr?.name.replace(/_/g, ' ');
|
|
|
// row.appendChild(nameTd);
|
|
|
|
|
|
// // 2. Source
|
|
|
// const sourceTd = el('td', 'attribute-source');
|
|
|
-// sourceTd.textContent = formatString(attr.source);
|
|
|
+// sourceTd.textContent = formatString(attr?.source);
|
|
|
// row.appendChild(sourceTd);
|
|
|
|
|
|
|
|
|
// // 3. Existing Value
|
|
|
// const originalTd = el('td', 'original-value');
|
|
|
-// originalTd.textContent = formatString(attr.originalValue);
|
|
|
+// originalTd.textContent = formatString(attr?.originalValue);
|
|
|
// row.appendChild(originalTd);
|
|
|
|
|
|
// // 4. AI Extracted Value (Highlight if mismatch)
|
|
|
-// const aiTd = el('td', `ai-value ${attr.aiValue ? '' : 'mismatch-value'}`);
|
|
|
-// aiTd.textContent = attr.aiValue;
|
|
|
+// const aiTd = el('td', `ai-value ${attr?.aiValue ? '' : 'mismatch-value'}`);
|
|
|
+// aiTd.textContent = attr?.aiValue;
|
|
|
// row.appendChild(aiTd);
|
|
|
|
|
|
|
|
|
// // 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';
|
|
|
+// // const matchPill = el('span', `pill status-pill status-${attr?.isMatch ? 'match' : 'mismatch'}`);
|
|
|
+// // matchPill.textContent = attr?.isMatch ? '✅ MATCH' : '❌ MISMATCH';
|
|
|
// // matchTd.appendChild(matchPill);
|
|
|
// // row.appendChild(matchTd);
|
|
|
|
|
|
@@ -1513,41 +1513,78 @@ function renderInlineForCards() {
|
|
|
function renderMandatoryComparisonTable(attributes, title, productType) {
|
|
|
const section = el('div', 'attribute-section');
|
|
|
|
|
|
- let attributeEntries = [];
|
|
|
+ // --- 1. Intermediate object for merging values ---
|
|
|
+ let mergedAttributes = {};
|
|
|
|
|
|
- // --- Build attributeEntries ---
|
|
|
+ // --- Build mergedAttributes ---
|
|
|
Object.keys(attributes).forEach(key => {
|
|
|
const valuesArray = Array.isArray(attributes[key]) ? attributes[key] : [attributes[key]];
|
|
|
+
|
|
|
+ if (!mergedAttributes[key]) {
|
|
|
+ mergedAttributes[key] = {
|
|
|
+ aiValues: new Set(),
|
|
|
+ originalValues: new Set(),
|
|
|
+ sources: new Set(),
|
|
|
+ reasons: new Set(),
|
|
|
+ // Store the first encountered config for possibleValues lookup
|
|
|
+ firstValue: valuesArray[0]
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
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);
|
|
|
-
|
|
|
- let possibleValues = [];
|
|
|
- if (attrConfig && attrConfig.possible_values) {
|
|
|
- possibleValues = attrConfig.possible_values.split(',').map(s => s.trim());
|
|
|
- }
|
|
|
+ // Add values to sets for unique collection
|
|
|
+ if (aiValue !== 'N/A') mergedAttributes[key].aiValues.add(aiValue);
|
|
|
+ if (originalValue !== 'N/A') mergedAttributes[key].originalValues.add(originalValue);
|
|
|
+ if (source !== 'N/A') mergedAttributes[key].sources.add(source);
|
|
|
+ if (reason !== 'N/A') mergedAttributes[key].reasons.add(reason);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // --- 2. Final attributeEntries from merged data ---
|
|
|
+ let attributeEntries = [];
|
|
|
|
|
|
- // Determine match flag
|
|
|
- const isFoundInPossible = possibleValues.includes(aiValue);
|
|
|
- const matchFlag = isFoundInPossible ? true : false;
|
|
|
+ Object.keys(mergedAttributes).forEach(key => {
|
|
|
+ const mergedData = mergedAttributes[key];
|
|
|
+
|
|
|
+ // Find possible values for this attribute from full data using the stored firstValue
|
|
|
+ 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());
|
|
|
+ }
|
|
|
|
|
|
- attributeEntries.push({
|
|
|
- name: key,
|
|
|
- aiValue: aiValue,
|
|
|
- possibleValues: possibleValues,
|
|
|
- originalValue: originalValue,
|
|
|
- source: source,
|
|
|
- isMatch: matchFlag,
|
|
|
- reason: reason
|
|
|
- });
|
|
|
+ // Get merged AI Value
|
|
|
+ const aiValueString = Array.from(mergedData.aiValues).join(', ');
|
|
|
+
|
|
|
+ // Determine match flag: check if ANY of the AI values are in possibleValues
|
|
|
+ const isFoundInPossible = Array.from(mergedData.aiValues).some(aiVal => possibleValues.includes(aiVal));
|
|
|
+ const matchFlag = isFoundInPossible ? true : false;
|
|
|
+
|
|
|
+ // Get merged Original Value, Source, and Reason strings
|
|
|
+ const originalValueString = Array.from(mergedData.originalValues).join(', ');
|
|
|
+ const sourceString = Array.from(mergedData.sources).join(', ');
|
|
|
+ const reasonString = Array.from(mergedData.reasons).join(' | ');
|
|
|
+
|
|
|
+ attributeEntries.push({
|
|
|
+ name: key,
|
|
|
+ aiValue: aiValueString || 'N/A', // Use merged string
|
|
|
+ possibleValues: possibleValues,
|
|
|
+ originalValue: originalValueString || 'N/A',
|
|
|
+ source: sourceString || 'N/A',
|
|
|
+ isMatch: matchFlag,
|
|
|
+ reason: reasonString || 'N/A'
|
|
|
});
|
|
|
});
|
|
|
|
|
|
+ // --- Rest of the function (unchanged) ---
|
|
|
+ // ... (Section title, empty check, splitting into two tables, and buildTable helper) ...
|
|
|
+
|
|
|
// --- Section title ---
|
|
|
const titleEl = el('div', 'section-title');
|
|
|
titleEl.innerHTML = `<h3>${title} (${attributeEntries.length})</h3>`;
|
|
|
@@ -1575,7 +1612,8 @@ function renderMandatoryComparisonTable(attributes, title, productType) {
|
|
|
|
|
|
const thead = el('thead');
|
|
|
const headerRow = el('tr');
|
|
|
- ['Attribute Name', 'AI Generated Value', 'Action'].forEach(text => {
|
|
|
+ // , 'AI Generated Value'
|
|
|
+ ['Attribute Name', 'Recommended Attribute Value(s)'].forEach(text => {
|
|
|
const th = el('th');
|
|
|
th.textContent = text;
|
|
|
headerRow.appendChild(th);
|
|
|
@@ -1586,80 +1624,47 @@ function renderMandatoryComparisonTable(attributes, title, productType) {
|
|
|
const tbody = el('tbody');
|
|
|
|
|
|
attrArray.forEach(attr => {
|
|
|
- const row = el('tr', attr.isMatch ? 'match-row' : 'mismatch-row');
|
|
|
+ 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, ' ');
|
|
|
+ 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);
|
|
|
+ select.setAttribute('data-attribute', attr?.name);
|
|
|
|
|
|
+ // The values selected will be the merged AI values
|
|
|
+ const selectedValues = attr?.aiValue.split(', ').filter(v => v !== 'N/A' && v.trim() !== '');
|
|
|
|
|
|
// Populate options
|
|
|
- attr.possibleValues.forEach(val => {
|
|
|
+ // Add possible values
|
|
|
+ attr?.possibleValues.forEach(val => {
|
|
|
const option = document.createElement('option');
|
|
|
option.value = val;
|
|
|
option.textContent = val;
|
|
|
- if (val === attr.aiValue) option.selected = true;
|
|
|
+ if (selectedValues.includes(val)) option.selected = true;
|
|
|
select.appendChild(option);
|
|
|
});
|
|
|
+
|
|
|
+ // Add AI values not found in possibleValues as new selected options
|
|
|
+ // selectedValues.forEach(aiVal => {
|
|
|
+ // if (!attr?.possibleValues.includes(aiVal)) {
|
|
|
+ // const newOpt = document.createElement('option');
|
|
|
+ // newOpt.value = aiVal;
|
|
|
+ // newOpt.textContent = aiVal;
|
|
|
+ // newOpt.selected = true;
|
|
|
+ // select.appendChild(newOpt);
|
|
|
+ // }
|
|
|
+ // });
|
|
|
|
|
|
- // 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);
|
|
|
@@ -1694,6 +1699,186 @@ function renderMandatoryComparisonTable(attributes, title, productType) {
|
|
|
|
|
|
// let attributeEntries = [];
|
|
|
|
|
|
+// // --- Build 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';
|
|
|
+// const reason = v.reason || 'N/A';
|
|
|
+
|
|
|
+// // Find possible values for this attribute from full data
|
|
|
+// 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 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: 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.`;
|
|
|
+// section.appendChild(msg);
|
|
|
+// return section;
|
|
|
+// }
|
|
|
+
|
|
|
+// // --- 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');
|
|
|
+// // , 'AI Generated Value'
|
|
|
+// ['Attribute Name', 'Recommended Attribute Value(s)'].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 => {
|
|
|
@@ -1754,16 +1939,16 @@ function renderMandatoryComparisonTable(attributes, title, productType) {
|
|
|
// const tbody = el('tbody');
|
|
|
|
|
|
// attributeEntries.forEach(attr => {
|
|
|
-// const row = el('tr', attr.isMatch ? 'match-row' : 'mismatch-row');
|
|
|
+// 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, ' ');
|
|
|
+// 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);
|
|
|
+// const sourceTd = el('td', `ai-value ${attr?.isMatch ? '' : 'mismatch-value'}`);
|
|
|
+// sourceTd.textContent = formatString(attr?.aiValue);
|
|
|
// row.appendChild(sourceTd);
|
|
|
|
|
|
|
|
|
@@ -1772,22 +1957,22 @@ function renderMandatoryComparisonTable(attributes, title, productType) {
|
|
|
|
|
|
// const select = document.createElement('select');
|
|
|
// select.classList.add('select2-dropdown');
|
|
|
-// select.setAttribute('data-attribute', attr.name);
|
|
|
+// select.setAttribute('data-attribute', attr?.name);
|
|
|
|
|
|
// // Populate possible values
|
|
|
-// attr.possibleValues.forEach(val => {
|
|
|
+// attr?.possibleValues.forEach(val => {
|
|
|
// const option = document.createElement('option');
|
|
|
// option.value = val;
|
|
|
// option.textContent = val;
|
|
|
-// if (val === attr.aiValue) option.selected = true;
|
|
|
+// 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') {
|
|
|
+// if (!attr?.isMatch && attr?.aiValue !== 'N/A') {
|
|
|
// const newOpt = document.createElement('option');
|
|
|
-// newOpt.value = attr.aiValue;
|
|
|
-// newOpt.textContent = attr.aiValue + " (new)";
|
|
|
+// newOpt.value = attr?.aiValue;
|
|
|
+// newOpt.textContent = attr?.aiValue + " (new)";
|
|
|
// newOpt.selected = true;
|
|
|
// select.appendChild(newOpt);
|
|
|
// }
|
|
|
@@ -1868,22 +2053,22 @@ function renderMandatoryComparisonTable(attributes, title, productType) {
|
|
|
|
|
|
// 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 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, ' ');
|
|
|
+// nameTd.textContent = formatString(attr?.name).replace(/_/g, ' ');
|
|
|
// row.appendChild(nameTd);
|
|
|
|
|
|
|
|
|
// // 3. AI Extracted Value
|
|
|
-// const aiTd = el('td', `ai-value ${attr.isMatch ? '' : 'mismatch-value'}`);
|
|
|
-// aiTd.textContent = attr.aiValue;
|
|
|
+// 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);
|
|
|
+// sourceTd.textContent = formatString(attr?.source);
|
|
|
// row.appendChild(sourceTd);
|
|
|
|
|
|
// tbody.appendChild(row);
|
|
|
@@ -1991,7 +2176,7 @@ function renderAttributesAsTable(attributes, title, mandatoryData = null) {
|
|
|
|
|
|
if (hasMandatoryBaseline) {
|
|
|
// Normalize the current attribute's value
|
|
|
- const currentValueNormalized = String(attr.value).trim().toLowerCase();
|
|
|
+ const currentValueNormalized = String(attr?.value).trim().toLowerCase();
|
|
|
|
|
|
// Check if the current value exists ANYWHERE in the mandatory original values set
|
|
|
if (mandatoryOriginalValuesSet.has(currentValueNormalized)) {
|
|
|
@@ -2003,11 +2188,11 @@ function renderAttributesAsTable(attributes, title, mandatoryData = null) {
|
|
|
// --- END CORE COMPARISON LOGIC ---
|
|
|
|
|
|
const nameTd = el('td', 'attribute-name');
|
|
|
- nameTd.textContent = formatString(attr.name).replace(/_/g, ' ');
|
|
|
+ nameTd.textContent = formatString(attr?.name).replace(/_/g, ' ');
|
|
|
row.appendChild(nameTd);
|
|
|
|
|
|
const valueTd = el('td', 'attribute-value');
|
|
|
- const displayValue = formatString(attr.value) + ' (' + formatString(attr.source) + ')';
|
|
|
+ const displayValue = formatString(attr?.value) + ' (' + formatString(attr?.source) + ')';
|
|
|
|
|
|
valueTd.textContent = displayValue || 'N/A';
|
|
|
|
|
|
@@ -2088,18 +2273,18 @@ function renderAttributesAsTable(attributes, title, mandatoryData = null) {
|
|
|
// const row = el('tr');
|
|
|
|
|
|
// const nameTd = el('td', 'attribute-name');
|
|
|
-// nameTd.textContent = formatString(attr.name).replace(/_/g, ' ');
|
|
|
+// nameTd.textContent = formatString(attr?.name).replace(/_/g, ' ');
|
|
|
// row.appendChild(nameTd);
|
|
|
|
|
|
// const valueTd = el('td', 'attribute-value');
|
|
|
-// const displayValue = Array.isArray(attr.value)
|
|
|
-// ? (attr.value.map(v => formatString(v.value))).join(', ') +'('+ formatString(attr.source)+ ')'
|
|
|
-// : formatString(attr.value) +'('+ formatString(attr.source)+ ')';
|
|
|
+// const displayValue = Array.isArray(attr?.value)
|
|
|
+// ? (attr?.value.map(v => formatString(v.value))).join(', ') +'('+ formatString(attr?.source)+ ')'
|
|
|
+// : formatString(attr?.value) +'('+ formatString(attr?.source)+ ')';
|
|
|
// valueTd.textContent = displayValue || 'N/A';
|
|
|
// row.appendChild(valueTd);
|
|
|
|
|
|
// // const sourceTd = el('td', 'attribute-source');
|
|
|
-// // sourceTd.textContent = attr.source || 'Unknown';
|
|
|
+// // sourceTd.textContent = attr?.source || 'Unknown';
|
|
|
// // row.appendChild(sourceTd);
|
|
|
|
|
|
// tbody.appendChild(row);
|
|
|
@@ -2215,6 +2400,18 @@ function renderAttributesAsTable(attributes, title, mandatoryData = null) {
|
|
|
// if (apiSummary) apiSummary.style.display = 'block';
|
|
|
// }
|
|
|
|
|
|
+function highlightMatches(text, keywords) {
|
|
|
+ if (!text) return '—';
|
|
|
+ let highlighted = text;
|
|
|
+ keywords.forEach(keyword => {
|
|
|
+ const escapedKeyword = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Escape regex
|
|
|
+ const regex = new RegExp(`(${escapedKeyword})`, 'gi');
|
|
|
+ highlighted = highlighted.replace(regex, '<span class="highlight-text">$1</span>');
|
|
|
+ });
|
|
|
+ return highlighted;
|
|
|
+}
|
|
|
+
|
|
|
+// WOrking ONe
|
|
|
function renderInlineForTable() {
|
|
|
const api = API_RESPONSE_AI;
|
|
|
const table = $('#tableContainer');
|
|
|
@@ -2244,7 +2441,7 @@ function renderInlineForTable() {
|
|
|
if (Object.keys(mandatoryData).length > 0) {
|
|
|
const mandatoryTable = renderMandatoryComparisonTable(
|
|
|
mandatoryData,
|
|
|
- 'Mandatory Attributes Comparison'
|
|
|
+ 'Attributes'
|
|
|
);
|
|
|
content.appendChild(mandatoryTable);
|
|
|
}
|
|
|
@@ -2312,12 +2509,45 @@ function renderInlineForTable() {
|
|
|
const c3 = el('span', 'pill secondary'); c3.textContent = `OCR Keys: ${ocrExtractedCount}`;
|
|
|
const c4 = el('span', 'pill secondary'); c4.textContent = `Visual Keys: ${visualExtractedCount}`;
|
|
|
counts.appendChild(c1); counts.appendChild(c2); counts.appendChild(c3); counts.appendChild(c4);
|
|
|
- content.appendChild(counts);
|
|
|
+ // content.appendChild(counts);
|
|
|
|
|
|
// Final assembly and insertion
|
|
|
td.appendChild(content);
|
|
|
detail.appendChild(td);
|
|
|
baseRow.insertAdjacentElement('afterend', detail);
|
|
|
+
|
|
|
+
|
|
|
+ // Extract mandatory values for highlighting
|
|
|
+ // const mandatoryValues = Object.values(mandatoryData)
|
|
|
+ // .map(v => v?.value?.toString()?.trim())
|
|
|
+ // .filter(Boolean);
|
|
|
+
|
|
|
+ const mandatoryValues = Object.values(mandatoryData)
|
|
|
+ // 1. Use flatMap to iterate over the outer array AND flatten the inner arrays.
|
|
|
+ // 'arr' here is the array like [ { value: 'Pullover', ... } ]
|
|
|
+ .flatMap(arr =>
|
|
|
+ // 2. Map the inner array. We safely assume the first element [0]
|
|
|
+ // holds the value we want.
|
|
|
+ arr.map(item => item?.value?.toString()?.trim())
|
|
|
+ )
|
|
|
+ // 3. Filter out any potential undefined/null/empty string results.
|
|
|
+ .filter(Boolean);
|
|
|
+
|
|
|
+ // Highlight in Product Name
|
|
|
+ const nameCell = baseRow.querySelector('td:nth-child(3)');
|
|
|
+ if (nameCell) {
|
|
|
+ nameCell.innerHTML = highlightMatches(nameCell.textContent, mandatoryValues);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Highlight in Short Description
|
|
|
+ const descCell = baseRow.querySelector('td:nth-child(6)');
|
|
|
+ if (descCell) {
|
|
|
+ const shortDescDiv = descCell.querySelector('.short-desc');
|
|
|
+ if (shortDescDiv) {
|
|
|
+ shortDescDiv.innerHTML = highlightMatches(shortDescDiv.innerHTML, mandatoryValues);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
});
|
|
|
|
|
|
// Update summary statistics
|
|
|
@@ -2327,7 +2557,21 @@ function renderInlineForTable() {
|
|
|
const apiSummary = $('#api-summary');
|
|
|
if (apiSummary) apiSummary.style.display = 'block';
|
|
|
}
|
|
|
+// working one
|
|
|
+function applyHighlightingToTableRow(baseRow, mandatoryValues) {
|
|
|
+ const nameCell = baseRow.querySelector('td:nth-child(3)');
|
|
|
+ if (nameCell) {
|
|
|
+ nameCell.innerHTML = highlightMatches(nameCell.textContent, mandatoryValues);
|
|
|
+ }
|
|
|
|
|
|
+ const descCell = baseRow.querySelector('td:nth-child(6)');
|
|
|
+ if (descCell) {
|
|
|
+ const shortDescDiv = descCell.querySelector('.short-desc');
|
|
|
+ if (shortDescDiv) {
|
|
|
+ shortDescDiv.innerHTML = highlightMatches(shortDescDiv.innerHTML, mandatoryValues);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
function renderInlineAttributes() {
|
|
|
if (layoutMode === 'cards') renderInlineForCards(); else renderInlineForTable();
|
|
|
@@ -2695,8 +2939,8 @@ function getAtributeList(){
|
|
|
// Step 1: Extract unique mandatory attribute names
|
|
|
const mandatoryAttributes = [...new Set(
|
|
|
attributesData
|
|
|
- .filter(attr => attr.is_mandatory === "Yes")
|
|
|
- .map(attr => attr.attribute_name)
|
|
|
+ .filter(attr => attr?.is_mandatory === "Yes")
|
|
|
+ .map(attr => attr?.attribute_name)
|
|
|
)];
|
|
|
|
|
|
// Step 2: Populate the select element
|
|
|
@@ -2795,11 +3039,11 @@ function renderMandatoryComparisonCards(attributes, title) {
|
|
|
|
|
|
attributeEntries.forEach(attr => {
|
|
|
// Main Card Element
|
|
|
- const card = el('div', `comparison-card ${attr.isMatch ? 'match' : 'mismatch-card'}`);
|
|
|
+ const card = el('div', `comparison-card ${attr?.isMatch ? 'match' : 'mismatch-card'}`);
|
|
|
|
|
|
// Card Header (Attribute Name)
|
|
|
const header = el('div', 'card-header');
|
|
|
- header.textContent = attr.name.replace(/_/g, ' ');
|
|
|
+ header.textContent = attr?.name.replace(/_/g, ' ');
|
|
|
card.appendChild(header);
|
|
|
|
|
|
// Content Wrapper
|
|
|
@@ -2809,25 +3053,25 @@ function renderMandatoryComparisonCards(attributes, title) {
|
|
|
const originalBox = el('div', 'value-box original-box');
|
|
|
originalBox.innerHTML = `
|
|
|
<div class="value-label">Manually Identified Value</div>
|
|
|
- <div class="value-text">${attr.originalValue}</div>
|
|
|
+ <div class="value-text">${attr?.originalValue}</div>
|
|
|
`;
|
|
|
content.appendChild(originalBox);
|
|
|
|
|
|
// AI Value Box
|
|
|
- const aiBox = el('div', `value-box ai-box ${attr.isMatch ? 'found-value' : 'mismatch-value'}`);
|
|
|
+ const aiBox = el('div', `value-box ai-box ${attr?.isMatch ? 'found-value' : 'mismatch-value'}`);
|
|
|
aiBox.innerHTML = `
|
|
|
- <div class="value-label">AI Generated Value <span class="value-source">(${attr.source})</span></div>
|
|
|
- <div class="value-text">${attr.aiValue}</div>
|
|
|
+ <div class="value-label">AI Generated Value <span class="value-source">(${attr?.source})</span></div>
|
|
|
+ <div class="value-text">${attr?.aiValue}</div>
|
|
|
`;
|
|
|
content.appendChild(aiBox);
|
|
|
|
|
|
card.appendChild(content);
|
|
|
|
|
|
// Mismatch Indicator (only visible on mismatch-card via CSS)
|
|
|
- if (!attr.isMatch) {
|
|
|
+ if (!attr?.isMatch) {
|
|
|
const indicator = el('div', 'mismatch-indicator');
|
|
|
// indicator.innerHTML = '❌ MISMATCH';
|
|
|
- indicator.innerHTML = attr.isMatch ? '✅ MATCH' : '❌ MISMATCH';
|
|
|
+ indicator.innerHTML = attr?.isMatch ? '✅ MATCH' : '❌ MISMATCH';
|
|
|
card.appendChild(indicator);
|
|
|
}
|
|
|
|
|
|
@@ -2911,10 +3155,10 @@ function renderMandatoryComparisonCards(attributes, title) {
|
|
|
// const cardsContainer = el('div', 'comparison-cards-container');
|
|
|
|
|
|
// attributeEntries.forEach(attr => {
|
|
|
-// const card = el('div', `comparison-card ${attr.isMatch ? 'match' : 'mismatch-card'}`);
|
|
|
+// const card = el('div', `comparison-card ${attr?.isMatch ? 'match' : 'mismatch-card'}`);
|
|
|
|
|
|
// const header = el('div', 'card-header');
|
|
|
-// header.textContent = attr.name.replace(/_/g, ' ');
|
|
|
+// header.textContent = attr?.name.replace(/_/g, ' ');
|
|
|
// card.appendChild(header);
|
|
|
|
|
|
// const content = el('div', 'card-content');
|
|
|
@@ -2923,25 +3167,25 @@ function renderMandatoryComparisonCards(attributes, title) {
|
|
|
// const originalBox = el('div', 'value-box original-box');
|
|
|
// originalBox.innerHTML = `
|
|
|
// <div class="value-label">Manually Identified Value</div>
|
|
|
-// <div class="value-text">${attr.originalValue}</div>
|
|
|
+// <div class="value-text">${attr?.originalValue}</div>
|
|
|
// `;
|
|
|
// content.appendChild(originalBox);
|
|
|
|
|
|
// // AI Value Box
|
|
|
-// const aiBox = el('div', `value-box ai-box ${attr.isMatch ? 'found-value' : 'mismatch-value'}`);
|
|
|
+// const aiBox = el('div', `value-box ai-box ${attr?.isMatch ? 'found-value' : 'mismatch-value'}`);
|
|
|
// aiBox.innerHTML = `
|
|
|
-// <div class="value-label">AI Generated Value <span class="value-source">(${attr.source})</span></div>
|
|
|
-// <div class="value-text">${attr.aiValue}</div>
|
|
|
+// <div class="value-label">AI Generated Value <span class="value-source">(${attr?.source})</span></div>
|
|
|
+// <div class="value-text">${attr?.aiValue}</div>
|
|
|
// `;
|
|
|
// content.appendChild(aiBox);
|
|
|
|
|
|
// card.appendChild(content);
|
|
|
|
|
|
// // Mismatch Indicator
|
|
|
-// if (!attr.isMatch) {
|
|
|
+// if (!attr?.isMatch) {
|
|
|
// const indicator = el('div', 'mismatch-indicator');
|
|
|
// // indicator.innerHTML = '❌ MISMATCH';
|
|
|
-// indicator.innerHTML = attr.isMatch ? '✅ MATCH' : '❌ MISMATCH';
|
|
|
+// indicator.innerHTML = attr?.isMatch ? '✅ MATCH' : '❌ MISMATCH';
|
|
|
// card.appendChild(indicator);
|
|
|
// }
|
|
|
|
|
|
@@ -2993,11 +3237,11 @@ function renderMandatoryComparisonCards(attributes, title) {
|
|
|
|
|
|
attributeEntries.forEach(attr => {
|
|
|
// --- CHANGE 1: Apply 'match-card' or 'mismatch-card' explicitly ---
|
|
|
- const cardClass = attr.isMatch ? 'match-card' : 'mismatch-card';
|
|
|
+ const cardClass = attr?.isMatch ? 'match-card' : 'mismatch-card';
|
|
|
const card = el('div', `comparison-card ${cardClass}`);
|
|
|
|
|
|
const header = el('div', 'card-header');
|
|
|
- header.textContent = attr.name.replace(/_/g, ' ');
|
|
|
+ header.textContent = attr?.name.replace(/_/g, ' ');
|
|
|
card.appendChild(header);
|
|
|
|
|
|
const content = el('div', 'card-content');
|
|
|
@@ -3006,16 +3250,16 @@ function renderMandatoryComparisonCards(attributes, title) {
|
|
|
const originalBox = el('div', 'value-box original-box');
|
|
|
originalBox.innerHTML = `
|
|
|
<div class="value-label">Manually Identified Value</div>
|
|
|
- <div class="value-text">${attr.originalValue}</div>
|
|
|
+ <div class="value-text">${attr?.originalValue}</div>
|
|
|
`;
|
|
|
content.appendChild(originalBox);
|
|
|
|
|
|
// AI Value Box
|
|
|
// Removed 'found-value' class here as styling should rely on the parent card class
|
|
|
- const aiBox = el('div', `value-box ai-box ${attr.isMatch ? '' : 'mismatch-value'}`);
|
|
|
+ const aiBox = el('div', `value-box ai-box ${attr?.isMatch ? '' : 'mismatch-value'}`);
|
|
|
aiBox.innerHTML = `
|
|
|
- <div class="value-label">AI Generated Value <span class="value-source">(${attr.source})</span></div>
|
|
|
- <div class="value-text">${attr.aiValue}</div>
|
|
|
+ <div class="value-label">AI Generated Value <span class="value-source">(${attr?.source})</span></div>
|
|
|
+ <div class="value-text">${attr?.aiValue}</div>
|
|
|
`;
|
|
|
content.appendChild(aiBox);
|
|
|
|
|
|
@@ -3023,7 +3267,7 @@ function renderMandatoryComparisonCards(attributes, title) {
|
|
|
|
|
|
// --- CHANGE 2: Display the indicator for ALL cards, controlling color via CSS ---
|
|
|
const indicator = el('div', 'match-status-indicator');
|
|
|
- indicator.innerHTML = attr.isMatch ? '✅ MATCH' : '❌ MISMATCH';
|
|
|
+ indicator.innerHTML = attr?.isMatch ? '✅ MATCH' : '❌ MISMATCH';
|
|
|
card.appendChild(indicator);
|
|
|
|
|
|
cardsContainer.appendChild(card);
|
|
|
@@ -3092,7 +3336,7 @@ function renderSimpleAttributeCards(attributes, title) {
|
|
|
|
|
|
// Card Header (Attribute Name)
|
|
|
const header = el('div', 'card-header');
|
|
|
- header.textContent = formatString(attr.name).replace(/_/g, ' ');
|
|
|
+ header.textContent = formatString(attr?.name).replace(/_/g, ' ');
|
|
|
card.appendChild(header);
|
|
|
|
|
|
// Content Wrapper
|
|
|
@@ -3100,13 +3344,13 @@ function renderSimpleAttributeCards(attributes, title) {
|
|
|
|
|
|
// Value Box
|
|
|
const valueBox = el('div', 'value-box single-value-box');
|
|
|
- const displayValue = Array.isArray(attr.value)
|
|
|
- ? (attr.value.map(v => formatString(v.value))).join(', ') +'('+ formatString(attr.source)+ ')'
|
|
|
- : formatString(attr.value) +'('+ formatString(attr.source)+ ')';
|
|
|
- // : attr.value;
|
|
|
+ const displayValue = Array.isArray(attr?.value)
|
|
|
+ ? (attr?.value.map(v => formatString(v.value))).join(', ') +'('+ formatString(attr?.source)+ ')'
|
|
|
+ : formatString(attr?.value) +'('+ formatString(attr?.source)+ ')';
|
|
|
+ // : attr?.value;
|
|
|
|
|
|
valueBox.innerHTML = `
|
|
|
- <div class="value-label">Extracted Value <span class="value-source">(${formatString(attr.source)})</span></div>
|
|
|
+ <div class="value-label">Extracted Value <span class="value-source">(${formatString(attr?.source)})</span></div>
|
|
|
<div class="value-text">${displayValue || 'N/A'}</div>
|
|
|
`;
|
|
|
content.appendChild(valueBox);
|