Skip to content

Commit b67fd39

Browse files
authored
Merge pull request #6 from RemcoHalman/claude/create-modules-variable-file-weRvk
feat: Add Modules tab with Bill of Materials
2 parents c168ef0 + af24913 commit b67fd39

3 files changed

Lines changed: 295 additions & 5 deletions

File tree

index.html

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ <h1>
4848
<button class="tab-button" data-tab="components">NMEA Components</button>
4949
<button class="tab-button" data-tab="alerts">Alerts</button>
5050
<button class="tab-button" data-tab="memory">Memory</button>
51+
<button class="tab-button" data-tab="modules">Modules</button>
5152
</div>
5253

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

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

130132
// Parse all data
131133
allUnits = await parseUnits(content);
134+
135+
// Enrich units with product numbers from modules.js
136+
const moduleLookup = new Map();
137+
MODULES.forEach(module => {
138+
moduleLookup.set(module.standardUnitVariantNumber, module);
139+
});
140+
141+
allUnits = allUnits.map(unit => {
142+
const moduleInfo = moduleLookup.get(unit.standardUnitVariantNumber);
143+
return {
144+
...unit,
145+
productNumber: moduleInfo ? moduleInfo.productNumber : 'Unknown'
146+
};
147+
});
148+
132149
currentUnits = allUnits;
133150
currentMetadata = parseProjectMetadata(content);
134151
currentAlarms = parseAlarms(content);
@@ -167,6 +184,9 @@ <h1>
167184
case 'memory':
168185
displayMemory(currentMemory, results, currentMetadata);
169186
break;
187+
case 'modules':
188+
displayModules(allUnits, results, currentMetadata, MODULES);
189+
break;
170190
}
171191
}
172192

@@ -188,10 +208,18 @@ <h1>
188208
// Hide search for non-units tabs
189209
if (activeTab !== 'units') {
190210
searchBox.style.display = 'none';
191-
metadataCard.style.display = 'none';
192211
} else {
193212
searchBox.style.display = 'block';
194-
metadataCard.style.display = 'block';
213+
}
214+
215+
// Show metadata card only for units tab
216+
const metadataCard = document.querySelector('.metadata-card');
217+
if (metadataCard) {
218+
if (activeTab === 'units') {
219+
metadataCard.style.display = 'block';
220+
} else {
221+
metadataCard.style.display = 'none';
222+
}
195223
}
196224
});
197225
});

js/ui.js

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ function renderUnitCard(unit, hasSearch = false) {
5959
<div class="detail-value">${escapeHtml(unit.unitTypeId)}</div>
6060
</div>
6161
<div class="detail-item">
62-
<div class="detail-label">Variant Number</div>
63-
<div class="detail-value">${escapeHtml(unit.standardUnitVariantNumber)}</div>
62+
<div class="detail-label">Product Number</div>
63+
<div class="detail-value">${escapeHtml(unit.productNumber || 'Unknown')}</div>
6464
</div>
6565
</div>
6666
@@ -437,4 +437,115 @@ export function displayMemory(memory, container, metadata = null) {
437437
html += '</tbody></table></div></div>';
438438

439439
container.innerHTML = html;
440+
}
441+
442+
/**
443+
* Display modules with product numbers and variant numbers
444+
* @param {Array} units - Array of unit objects
445+
* @param {HTMLElement} container - Container element
446+
* @param {Object} metadata - Optional project metadata
447+
* @param {Array} modulesList - Array of module definitions from modules.js
448+
*/
449+
export function displayModules(units, container, metadata = null, modulesList = []) {
450+
container.style.display = 'block';
451+
452+
let html = '';
453+
454+
if (metadata) {
455+
html += renderMetadata(metadata);
456+
}
457+
458+
// Create a lookup map from the modules list - MATCH ON VARIANT NUMBER
459+
const moduleLookup = new Map();
460+
modulesList.forEach(module => {
461+
moduleLookup.set(module.standardUnitVariantNumber, module);
462+
});
463+
464+
// Enrich units with product numbers from modules.js based on variant number
465+
const enrichedUnits = units.map(unit => {
466+
const variantNumber = unit.standardUnitVariantNumber;
467+
const moduleInfo = moduleLookup.get(variantNumber);
468+
469+
return {
470+
...unit,
471+
productNumber: moduleInfo ? moduleInfo.productNumber : 'Unknown',
472+
moduleDescription: moduleInfo ? moduleInfo.description : ''
473+
};
474+
});
475+
476+
// Generate Bill of Materials from enriched units
477+
const bom = generateBOMFromUnits(enrichedUnits);
478+
479+
// Bill of Materials Section - showing what's actually used in the loaded file
480+
html += '<div style="padding: 20px; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">';
481+
html += '<h3>📋 Bill of Materials</h3>';
482+
html += `<p style="margin-bottom: 15px; color: #666;">Total items: ${bom.reduce((sum, item) => sum + item.quantity, 0)} | Unique products: ${bom.length}</p>`;
483+
html += '<div style="overflow-x: auto;"><table><thead><tr>';
484+
html += '<th>Quantity</th><th>Product Number</th>';
485+
html += '</tr></thead><tbody>';
486+
487+
bom.forEach(item => {
488+
html += '<tr>';
489+
html += `<td style="text-align: center; font-weight: bold;">${item.quantity}</td>`;
490+
html += `<td>${escapeHtml(item.productNumber)}</td>`;
491+
html += '</tr>';
492+
});
493+
494+
html += '</tbody></table></div></div>';
495+
496+
container.innerHTML = html;
497+
}
498+
499+
/**
500+
* Get unique modules from units array
501+
* @param {Array} units - Array of unit objects
502+
* @returns {Array} Array of unique modules
503+
*/
504+
function getUniqueModulesFromUnits(units) {
505+
const uniqueMap = new Map();
506+
507+
units.forEach(unit => {
508+
const key = `${unit.name}|${unit.standardUnitVariantNumber || 'N/A'}`;
509+
if (!uniqueMap.has(key)) {
510+
uniqueMap.set(key, {
511+
productNumber: unit.name,
512+
variantNumber: unit.standardUnitVariantNumber || 'N/A',
513+
unitTypeId: unit.unitTypeId || 'N/A'
514+
});
515+
}
516+
});
517+
518+
return Array.from(uniqueMap.values()).sort((a, b) =>
519+
a.productNumber.localeCompare(b.productNumber)
520+
);
521+
}
522+
523+
/**
524+
* Generate Bill of Materials from units array
525+
* @param {Array} units - Array of unit objects (can be enriched with productNumber)
526+
* @returns {Array} BOM entries with product number, variant number, unit type, and quantity
527+
*/
528+
function generateBOMFromUnits(units) {
529+
const bomMap = new Map();
530+
531+
units.forEach(unit => {
532+
// Use variant number as the key for proper aggregation
533+
const key = unit.standardUnitVariantNumber || unit.name;
534+
535+
if (bomMap.has(key)) {
536+
bomMap.get(key).quantity++;
537+
} else {
538+
bomMap.set(key, {
539+
productNumber: unit.productNumber || 'Unknown',
540+
variantNumber: unit.standardUnitVariantNumber || 'N/A',
541+
unitName: unit.name,
542+
unitTypeId: unit.unitTypeId || 'N/A',
543+
quantity: 1
544+
});
545+
}
546+
});
547+
548+
return Array.from(bomMap.values()).sort((a, b) =>
549+
(a.productNumber || 'Unknown').localeCompare(b.productNumber || 'Unknown')
550+
);
440551
}

modules.js

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/**
2+
* Modules Variable File
3+
* Maps product numbers (unit names) to their standard unit variant numbers
4+
*
5+
* This file serves as a lookup table for generating Bill of Materials (BOM)
6+
* and enriching unit data with product information.
7+
*/
8+
9+
/**
10+
* Module definition
11+
* @typedef {Object} Module
12+
* @property {string} productNumber - The product number (unit name)
13+
* @property {string} standardUnitVariantNumber - The standard unit variant number
14+
* @property {string} description - Optional description of the module
15+
*/
16+
17+
/**
18+
* All available modules from EBP files
19+
* @type {Module[]}
20+
*/
21+
export const MODULES = [
22+
// From connect50_v-1+.ebp
23+
{ productNumber: "010-02575-10_mcuv2", standardUnitVariantNumber: "100257510", description: "MCU v2" },
24+
{ productNumber: "010-02223-11_mcu100", standardUnitVariantNumber: "2051011", description: "MCU 100" },
25+
{ productNumber: "010-02225-10", standardUnitVariantNumber: "2110110", description: "" },
26+
{ productNumber: "010-02225-16", standardUnitVariantNumber: "2110115", description: "" },
27+
{ productNumber: "010-02225-17", standardUnitVariantNumber: "2110116", description: "" },
28+
{ productNumber: "010-02225-18", standardUnitVariantNumber: "2110117", description: "" },
29+
{ productNumber: "010-02275-01", standardUnitVariantNumber: "0152", description: "" },
30+
{ productNumber: "010-02275-02", standardUnitVariantNumber: "0151", description: "" },
31+
{ productNumber: "010-02225-19", standardUnitVariantNumber: "2110118", description: "" },
32+
{ productNumber: "010-02275-03", standardUnitVariantNumber: "0152", description: "" },
33+
{ productNumber: "010-02225-05", standardUnitVariantNumber: "2110103", description: "" },
34+
{ productNumber: "010-02225-06", standardUnitVariantNumber: "2110104", description: "" },
35+
{ productNumber: "010-02225-07", standardUnitVariantNumber: "2110105", description: "" },
36+
{ productNumber: "010-02279-02", standardUnitVariantNumber: "2210102", description: "" },
37+
{ productNumber: "010-02279-03", standardUnitVariantNumber: "2210103", description: "" },
38+
39+
// From connect50 v2_v-1+.ebp
40+
{ productNumber: "010-02575-10", standardUnitVariantNumber: "100257510", description: "MCU v2" },
41+
{ productNumber: "MFD / WDU", standardUnitVariantNumber: "88888888", description: "MFD / WDU" },
42+
{ productNumber: "010-02225-30", standardUnitVariantNumber: "100222530", description: "" },
43+
{ productNumber: "010-02225-31", standardUnitVariantNumber: "100222531", description: "" },
44+
{ productNumber: "010-02278-21", standardUnitVariantNumber: "100227821", description: "" },
45+
{ productNumber: "010-02279-20", standardUnitVariantNumber: "100227920", description: "" },
46+
{ productNumber: "010-02279-21", standardUnitVariantNumber: "100227921", description: "" },
47+
48+
// From DCM.ebp
49+
{ productNumber: "MCU v2", standardUnitVariantNumber: "100257510", description: "MCU v2" },
50+
{ productNumber: "010-02219-01", standardUnitVariantNumber: "11", description: "" },
51+
{ productNumber: "010-02219-02", standardUnitVariantNumber: "12", description: "" },
52+
{ productNumber: "010-02219-03", standardUnitVariantNumber: "13", description: "" },
53+
{ productNumber: "010-02219-04", standardUnitVariantNumber: "14", description: "" },
54+
{ productNumber: "010-02219-05", standardUnitVariantNumber: "15", description: "" },
55+
{ productNumber: "010-02219-55", standardUnitVariantNumber: "55", description: "" },
56+
{ productNumber: "010-02220-06", standardUnitVariantNumber: "16", description: "" },
57+
{ productNumber: "010-02220-07", standardUnitVariantNumber: "17", description: "" },
58+
{ productNumber: "010-02220-08", standardUnitVariantNumber: "18", description: "" },
59+
{ productNumber: "010-02220-09", standardUnitVariantNumber: "19", description: "" },
60+
{ productNumber: "010-02220-10", standardUnitVariantNumber: "20", description: "" },
61+
{ productNumber: "010-02221-08", standardUnitVariantNumber: "28", description: "" },
62+
{ productNumber: "010-02222-10", standardUnitVariantNumber: "30", description: "" }
63+
];
64+
65+
/**
66+
* Create a lookup map for quick access by product number
67+
* @type {Map<string, Module>}
68+
*/
69+
export const MODULE_MAP = new Map(
70+
MODULES.map(module => [module.productNumber, module])
71+
);
72+
73+
/**
74+
* Create a reverse lookup map by standard unit variant number
75+
* @type {Map<string, Module[]>}
76+
*/
77+
export const VARIANT_MAP = MODULES.reduce((map, module) => {
78+
const variant = module.standardUnitVariantNumber;
79+
if (!map.has(variant)) {
80+
map.set(variant, []);
81+
}
82+
map.get(variant).push(module);
83+
return map;
84+
}, new Map());
85+
86+
/**
87+
* Get module information by product number
88+
* @param {string} productNumber - The product number to look up
89+
* @returns {Module|undefined} The module information or undefined if not found
90+
*/
91+
export function getModuleByProductNumber(productNumber) {
92+
return MODULE_MAP.get(productNumber);
93+
}
94+
95+
/**
96+
* Get module(s) by standard unit variant number
97+
* @param {string} variantNumber - The variant number to look up
98+
* @returns {Module[]} Array of modules with this variant number
99+
*/
100+
export function getModulesByVariantNumber(variantNumber) {
101+
return VARIANT_MAP.get(variantNumber) || [];
102+
}
103+
104+
/**
105+
* Generate a Bill of Materials from an array of units
106+
* @param {Array} units - Array of unit objects with name and standardUnitVariantNumber
107+
* @returns {Array} BOM entries with product number, variant number, and quantity
108+
*/
109+
export function generateBOM(units) {
110+
const bomMap = new Map();
111+
112+
units.forEach(unit => {
113+
const key = `${unit.name}|${unit.standardUnitVariantNumber || ''}`;
114+
115+
if (bomMap.has(key)) {
116+
bomMap.get(key).quantity++;
117+
} else {
118+
bomMap.set(key, {
119+
productNumber: unit.name,
120+
standardUnitVariantNumber: unit.standardUnitVariantNumber || 'N/A',
121+
quantity: 1,
122+
serial: unit.serial || '0',
123+
unitTypeId: unit.unitTypeId || 'N/A'
124+
});
125+
}
126+
});
127+
128+
return Array.from(bomMap.values()).sort((a, b) =>
129+
a.productNumber.localeCompare(b.productNumber)
130+
);
131+
}
132+
133+
/**
134+
* Get unique modules from the MODULES array
135+
* Removes duplicates based on product number and variant number combination
136+
* @returns {Module[]} Array of unique modules
137+
*/
138+
export function getUniqueModules() {
139+
const uniqueMap = new Map();
140+
141+
MODULES.forEach(module => {
142+
const key = `${module.productNumber}|${module.standardUnitVariantNumber}`;
143+
if (!uniqueMap.has(key)) {
144+
uniqueMap.set(key, module);
145+
}
146+
});
147+
148+
return Array.from(uniqueMap.values()).sort((a, b) =>
149+
a.productNumber.localeCompare(b.productNumber)
150+
);
151+
}

0 commit comments

Comments
 (0)