Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ <h1>
<button class="tab-button" data-tab="components">NMEA Components</button>
<button class="tab-button" data-tab="alerts">Alerts</button>
<button class="tab-button" data-tab="memory">Memory</button>
<button class="tab-button" data-tab="modules">Modules</button>
</div>

<!-- Results container -->
Expand All @@ -65,8 +66,9 @@ <h1>
<!-- Import modules -->
<script type="module">
import { parseUnits, parseProjectMetadata, parseAlarms, parseComponents, parseMemory, validateEBP } from './js/parser.js';
import { displayUnits, displayComponents, displayAlertsDetailed, displayMemory, displayError } from './js/ui.js';
import { displayUnits, displayComponents, displayAlertsDetailed, displayMemory, displayModules, displayError } from './js/ui.js';
import { downloadPDF, filterUnitsAndChannels, debounce } from './js/utils.js';
import { MODULES, getModuleByProductNumber } from './modules.js';

// DOM elements
const dropZone = document.getElementById('dropZone');
Expand Down Expand Up @@ -129,6 +131,21 @@ <h1>

// Parse all data
allUnits = await parseUnits(content);

// Enrich units with product numbers from modules.js
const moduleLookup = new Map();
MODULES.forEach(module => {
moduleLookup.set(module.standardUnitVariantNumber, module);
});

allUnits = allUnits.map(unit => {
const moduleInfo = moduleLookup.get(unit.standardUnitVariantNumber);
return {
...unit,
productNumber: moduleInfo ? moduleInfo.productNumber : 'Unknown'
};
});

currentUnits = allUnits;
currentMetadata = parseProjectMetadata(content);
currentAlarms = parseAlarms(content);
Expand Down Expand Up @@ -167,6 +184,9 @@ <h1>
case 'memory':
displayMemory(currentMemory, results, currentMetadata);
break;
case 'modules':
displayModules(allUnits, results, currentMetadata, MODULES);
break;
}
}

Expand All @@ -188,10 +208,18 @@ <h1>
// Hide search for non-units tabs
if (activeTab !== 'units') {
searchBox.style.display = 'none';
metadataCard.style.display = 'none';
} else {
searchBox.style.display = 'block';
metadataCard.style.display = 'block';
}

// Show metadata card only for units tab
const metadataCard = document.querySelector('.metadata-card');
if (metadataCard) {
if (activeTab === 'units') {
metadataCard.style.display = 'block';
} else {
metadataCard.style.display = 'none';
}
}
});
});
Expand Down
115 changes: 113 additions & 2 deletions js/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ function renderUnitCard(unit, hasSearch = false) {
<div class="detail-value">${escapeHtml(unit.unitTypeId)}</div>
</div>
<div class="detail-item">
<div class="detail-label">Variant Number</div>
<div class="detail-value">${escapeHtml(unit.standardUnitVariantNumber)}</div>
<div class="detail-label">Product Number</div>
<div class="detail-value">${escapeHtml(unit.productNumber || 'Unknown')}</div>
</div>
</div>

Expand Down Expand Up @@ -437,4 +437,115 @@ export function displayMemory(memory, container, metadata = null) {
html += '</tbody></table></div></div>';

container.innerHTML = html;
}

/**
* Display modules with product numbers and variant numbers
* @param {Array} units - Array of unit objects
* @param {HTMLElement} container - Container element
* @param {Object} metadata - Optional project metadata
* @param {Array} modulesList - Array of module definitions from modules.js
*/
export function displayModules(units, container, metadata = null, modulesList = []) {
container.style.display = 'block';

let html = '';

if (metadata) {
html += renderMetadata(metadata);
}

// Create a lookup map from the modules list - MATCH ON VARIANT NUMBER
const moduleLookup = new Map();
modulesList.forEach(module => {
moduleLookup.set(module.standardUnitVariantNumber, module);
});

// Enrich units with product numbers from modules.js based on variant number
const enrichedUnits = units.map(unit => {
const variantNumber = unit.standardUnitVariantNumber;
const moduleInfo = moduleLookup.get(variantNumber);

return {
...unit,
productNumber: moduleInfo ? moduleInfo.productNumber : 'Unknown',
moduleDescription: moduleInfo ? moduleInfo.description : ''
};
});

// Generate Bill of Materials from enriched units
const bom = generateBOMFromUnits(enrichedUnits);

// Bill of Materials Section - showing what's actually used in the loaded file
html += '<div style="padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">';
html += '<h3>📋 Bill of Materials</h3>';
html += `<p style="margin-bottom: 15px; color: #666;">Total items: ${bom.reduce((sum, item) => sum + item.quantity, 0)} | Unique products: ${bom.length}</p>`;
html += '<div style="overflow-x: auto;"><table><thead><tr>';
html += '<th>Quantity</th><th>Product Number</th>';
html += '</tr></thead><tbody>';

bom.forEach(item => {
html += '<tr>';
html += `<td style="text-align: center; font-weight: bold;">${item.quantity}</td>`;
html += `<td>${escapeHtml(item.productNumber)}</td>`;
html += '</tr>';
});

html += '</tbody></table></div></div>';

container.innerHTML = html;
}

/**
* Get unique modules from units array
* @param {Array} units - Array of unit objects
* @returns {Array} Array of unique modules
*/
function getUniqueModulesFromUnits(units) {
const uniqueMap = new Map();

units.forEach(unit => {
const key = `${unit.name}|${unit.standardUnitVariantNumber || 'N/A'}`;
if (!uniqueMap.has(key)) {
uniqueMap.set(key, {
productNumber: unit.name,
variantNumber: unit.standardUnitVariantNumber || 'N/A',
unitTypeId: unit.unitTypeId || 'N/A'
});
}
});

return Array.from(uniqueMap.values()).sort((a, b) =>
a.productNumber.localeCompare(b.productNumber)
);
}

/**
* Generate Bill of Materials from units array
* @param {Array} units - Array of unit objects (can be enriched with productNumber)
* @returns {Array} BOM entries with product number, variant number, unit type, and quantity
*/
function generateBOMFromUnits(units) {
const bomMap = new Map();

units.forEach(unit => {
// Use variant number as the key for proper aggregation
const key = unit.standardUnitVariantNumber || unit.name;

if (bomMap.has(key)) {
bomMap.get(key).quantity++;
} else {
bomMap.set(key, {
productNumber: unit.productNumber || 'Unknown',
variantNumber: unit.standardUnitVariantNumber || 'N/A',
unitName: unit.name,
unitTypeId: unit.unitTypeId || 'N/A',
quantity: 1
});
}
});

return Array.from(bomMap.values()).sort((a, b) =>
(a.productNumber || 'Unknown').localeCompare(b.productNumber || 'Unknown')
);
}
151 changes: 151 additions & 0 deletions modules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/**
* Modules Variable File
* Maps product numbers (unit names) to their standard unit variant numbers
*
* This file serves as a lookup table for generating Bill of Materials (BOM)
* and enriching unit data with product information.
*/

/**
* Module definition
* @typedef {Object} Module
* @property {string} productNumber - The product number (unit name)
* @property {string} standardUnitVariantNumber - The standard unit variant number
* @property {string} description - Optional description of the module
*/

/**
* All available modules from EBP files
* @type {Module[]}
*/
export const MODULES = [
// From connect50_v-1+.ebp
{ productNumber: "010-02575-10_mcuv2", standardUnitVariantNumber: "100257510", description: "MCU v2" },
{ productNumber: "010-02223-11_mcu100", standardUnitVariantNumber: "2051011", description: "MCU 100" },
{ productNumber: "010-02225-10", standardUnitVariantNumber: "2110110", description: "" },
{ productNumber: "010-02225-16", standardUnitVariantNumber: "2110115", description: "" },
{ productNumber: "010-02225-17", standardUnitVariantNumber: "2110116", description: "" },
{ productNumber: "010-02225-18", standardUnitVariantNumber: "2110117", description: "" },
{ productNumber: "010-02275-01", standardUnitVariantNumber: "0152", description: "" },
{ productNumber: "010-02275-02", standardUnitVariantNumber: "0151", description: "" },
{ productNumber: "010-02225-19", standardUnitVariantNumber: "2110118", description: "" },
{ productNumber: "010-02275-03", standardUnitVariantNumber: "0152", description: "" },
{ productNumber: "010-02225-05", standardUnitVariantNumber: "2110103", description: "" },
{ productNumber: "010-02225-06", standardUnitVariantNumber: "2110104", description: "" },
{ productNumber: "010-02225-07", standardUnitVariantNumber: "2110105", description: "" },
{ productNumber: "010-02279-02", standardUnitVariantNumber: "2210102", description: "" },
{ productNumber: "010-02279-03", standardUnitVariantNumber: "2210103", description: "" },

// From connect50 v2_v-1+.ebp
{ productNumber: "010-02575-10", standardUnitVariantNumber: "100257510", description: "MCU v2" },
{ productNumber: "MFD / WDU", standardUnitVariantNumber: "88888888", description: "MFD / WDU" },
{ productNumber: "010-02225-30", standardUnitVariantNumber: "100222530", description: "" },
{ productNumber: "010-02225-31", standardUnitVariantNumber: "100222531", description: "" },
{ productNumber: "010-02278-21", standardUnitVariantNumber: "100227821", description: "" },
{ productNumber: "010-02279-20", standardUnitVariantNumber: "100227920", description: "" },
{ productNumber: "010-02279-21", standardUnitVariantNumber: "100227921", description: "" },

// From DCM.ebp
{ productNumber: "MCU v2", standardUnitVariantNumber: "100257510", description: "MCU v2" },
{ productNumber: "010-02219-01", standardUnitVariantNumber: "11", description: "" },
{ productNumber: "010-02219-02", standardUnitVariantNumber: "12", description: "" },
{ productNumber: "010-02219-03", standardUnitVariantNumber: "13", description: "" },
{ productNumber: "010-02219-04", standardUnitVariantNumber: "14", description: "" },
{ productNumber: "010-02219-05", standardUnitVariantNumber: "15", description: "" },
{ productNumber: "010-02219-55", standardUnitVariantNumber: "55", description: "" },
{ productNumber: "010-02220-06", standardUnitVariantNumber: "16", description: "" },
{ productNumber: "010-02220-07", standardUnitVariantNumber: "17", description: "" },
{ productNumber: "010-02220-08", standardUnitVariantNumber: "18", description: "" },
{ productNumber: "010-02220-09", standardUnitVariantNumber: "19", description: "" },
{ productNumber: "010-02220-10", standardUnitVariantNumber: "20", description: "" },
{ productNumber: "010-02221-08", standardUnitVariantNumber: "28", description: "" },
{ productNumber: "010-02222-10", standardUnitVariantNumber: "30", description: "" }
];

/**
* Create a lookup map for quick access by product number
* @type {Map<string, Module>}
*/
export const MODULE_MAP = new Map(
MODULES.map(module => [module.productNumber, module])
);

/**
* Create a reverse lookup map by standard unit variant number
* @type {Map<string, Module[]>}
*/
export const VARIANT_MAP = MODULES.reduce((map, module) => {
const variant = module.standardUnitVariantNumber;
if (!map.has(variant)) {
map.set(variant, []);
}
map.get(variant).push(module);
return map;
}, new Map());

/**
* Get module information by product number
* @param {string} productNumber - The product number to look up
* @returns {Module|undefined} The module information or undefined if not found
*/
export function getModuleByProductNumber(productNumber) {
return MODULE_MAP.get(productNumber);
}

/**
* Get module(s) by standard unit variant number
* @param {string} variantNumber - The variant number to look up
* @returns {Module[]} Array of modules with this variant number
*/
export function getModulesByVariantNumber(variantNumber) {
return VARIANT_MAP.get(variantNumber) || [];
}

/**
* Generate a Bill of Materials from an array of units
* @param {Array} units - Array of unit objects with name and standardUnitVariantNumber
* @returns {Array} BOM entries with product number, variant number, and quantity
*/
export function generateBOM(units) {
const bomMap = new Map();

units.forEach(unit => {
const key = `${unit.name}|${unit.standardUnitVariantNumber || ''}`;

if (bomMap.has(key)) {
bomMap.get(key).quantity++;
} else {
bomMap.set(key, {
productNumber: unit.name,
standardUnitVariantNumber: unit.standardUnitVariantNumber || 'N/A',
quantity: 1,
serial: unit.serial || '0',
unitTypeId: unit.unitTypeId || 'N/A'
});
}
});

return Array.from(bomMap.values()).sort((a, b) =>
a.productNumber.localeCompare(b.productNumber)
);
}

/**
* Get unique modules from the MODULES array
* Removes duplicates based on product number and variant number combination
* @returns {Module[]} Array of unique modules
*/
export function getUniqueModules() {
const uniqueMap = new Map();

MODULES.forEach(module => {
const key = `${module.productNumber}|${module.standardUnitVariantNumber}`;
if (!uniqueMap.has(key)) {
uniqueMap.set(key, module);
}
});

return Array.from(uniqueMap.values()).sort((a, b) =>
a.productNumber.localeCompare(b.productNumber)
);
}