/**
* UI Orchestration logic with Config support, 4 converter modes,
* page navigation, and Watcher integration.
*/
window.EDIBridge = window.EDIBridge || {};
class App {
constructor() {
this.mode = 'outbound-bosch';
this.currentPage = 'converter';
this.currentData = null;
this.rawContent = null;
this.masterData = {};
this.config = null;
this.configFileName = 'config.txt';
this.watcher = null;
this.werksWatcher = null;
this.initElements();
this.bindEvents();
this.loadConfigFromStorage();
this.initWatcher();
this.initOutboundWatcher();
this.initWerksWatcher();
// Initialize Sub-modules
if (window.EDIBridge.Viewer) {
this.viewer = new window.EDIBridge.Viewer(this);
}
if (window.EDIBridge.Editor) {
this.editor = new window.EDIBridge.Editor(this);
}
if (window.EDIBridge.HistoryManager) {
this.historyManager = new window.EDIBridge.HistoryManager(this);
}
}
// ─── Elements ────────────────────────────────────────────────────
initElements() {
this.dropZone = document.getElementById('dropZone');
this.fileInput = document.getElementById('fileInput');
this.configInput = document.getElementById('configInput');
this.enrichmentSection = document.getElementById('enrichmentSection');
this.resultSection = document.getElementById('resultSection');
this.previewArea = document.getElementById('previewArea');
this.dropText = document.getElementById('dropText');
this.configStatus = document.getElementById('configStatus');
this.statsGrid = document.getElementById('stats-grid');
this.ownCustomerNumber = document.getElementById('ownCustomerNumber');
}
// ─── Events ──────────────────────────────────────────────────────
bindEvents() {
if (this.dropZone) {
this.dropZone.onclick = (e) => { e.stopPropagation(); if (this.fileInput) this.fileInput.click(); };
this.dropZone.ondragover = (e) => { e.preventDefault(); this.dropZone.classList.add('dragover'); };
this.dropZone.ondragleave = () => this.dropZone.classList.remove('dragover');
this.dropZone.ondrop = (e) => {
e.preventDefault();
this.dropZone.classList.remove('dragover');
this.handleFile(e.dataTransfer.files[0]);
};
}
if (this.fileInput) {
this.fileInput.onchange = (e) => this.handleFile(e.target.files[0]);
}
if (this.configInput) {
this.configInput.onchange = (e) => this.loadConfig(e.target.files[0]);
}
}
// ─── Watcher Init ────────────────────────────────────────────────
initWatcher() {
const WatcherBridge = window.EDIBridge.WatcherBridge;
if (!WatcherBridge) return;
this.watcher = new WatcherBridge();
this.watcher.onStatusChange = (status) => this.updateWatcherUI(status);
this.watcher.onLogEntry = (entry) => this.appendLogEntry(entry);
// Check if running in Electron
if (!this.watcher.isElectron()) {
const notice = document.getElementById('electronNotice');
if (notice) notice.style.display = 'flex';
// Disable folder buttons
const btnIn = document.getElementById('btnSelectInput');
const btnOut = document.getElementById('btnSelectOutput');
const btnInvrptOut = document.getElementById('btnSelectInvrptOutput');
const btnStart = document.getElementById('btnStartWatcher');
if (btnIn) btnIn.disabled = true;
if (btnOut) btnOut.disabled = true;
if (btnInvrptOut) btnInvrptOut.disabled = true;
if (btnStart) btnStart.disabled = true;
} else {
// Load saved settings
this.watcher.loadSettings().then(settings => {
if (settings.inputDir) {
document.getElementById('inputDirPath').value = settings.inputDir;
}
if (settings.outputDir) {
document.getElementById('outputDirPath').value = settings.outputDir;
}
if (settings.invrptOutputDir) {
document.getElementById('invrptOutputPath').value = settings.invrptOutputDir;
}
});
}
}
initOutboundWatcher() {
const OutboundWatcherBridge = window.EDIBridge.OutboundWatcherBridge;
if (!OutboundWatcherBridge) return;
this.outboundWatcher = new OutboundWatcherBridge();
this.outboundWatcher.onStatusChange = (status) => this.updateOutboundWatcherUI(status);
this.outboundWatcher.onLogEntry = (entry) => this.appendLogEntry(entry);
if (!this.outboundWatcher.isElectron()) return;
// Load saved settings
window.electronAPI.loadSettings().then(settings => {
if (settings.outboundInputDir) {
document.getElementById('outboundInputPath').value = settings.outboundInputDir;
}
if (settings.outboundOutputDir) {
document.getElementById('outboundOutputPath').value = settings.outboundOutputDir;
}
});
}
initWerksWatcher() {
const WerksWatcherBridge = window.EDIBridge.WerksWatcherBridge;
if (!WerksWatcherBridge) return;
this.werksWatcher = new WerksWatcherBridge();
this.werksWatcher.onStatusChange = (status) => this.updateWerksWatcherUI(status);
this.werksWatcher.onLogEntry = (entry) => this.appendLogEntry(entry);
if (!this.werksWatcher.isElectron()) return;
// Load saved settings
window.electronAPI.loadSettings().then(settings => {
if (settings.werksInputDir) {
document.getElementById('werksInputPath').value = settings.werksInputDir;
}
if (settings.werksOutputDir) {
document.getElementById('werksOutputPath').value = settings.werksOutputDir;
}
});
}
showPage(page) {
this.currentPage = page;
document.querySelectorAll('.page-nav-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.page === page);
});
document.getElementById('pageConverter').style.display = page === 'converter' ? '' : 'none';
document.getElementById('pageSettings').style.display = page === 'settings' ? '' : 'none';
const pageViewer = document.getElementById('pageViewer');
if (pageViewer) pageViewer.style.display = page === 'viewer' ? '' : 'none';
const pageEditor = document.getElementById('pageEditor');
if (pageEditor) pageEditor.style.display = page === 'editor' ? '' : 'none';
const pageHistory = document.getElementById('pageHistory');
if (pageHistory) pageHistory.style.display = page === 'history' ? '' : 'none';
// Activate history manager when page is shown
if (page === 'history' && this.historyManager) {
this.historyManager.activate();
}
if (window.lucide) try { lucide.createIcons(); } catch (e) { }
}
// ─── Config ──────────────────────────────────────────────────────
async loadConfigFromStorage() {
let stored = null;
// Try getting config from Database first
if (window.electronAPI && window.electronAPI.dbGetConfig) {
try {
stored = await window.electronAPI.dbGetConfig();
if (stored) console.log("Config loaded from Database");
} catch (e) {
console.error("Failed to load config from DB:", e);
}
}
// Fallback to localStorage if DB doesn't have it
if (!stored) {
stored = localStorage.getItem('ediConfig');
if (stored) console.log("Config loaded from LocalStorage fallback");
}
if (stored) {
const ConfigParser = window.EDIBridge.ConfigParser;
this.config = ConfigParser.parse(stored);
if (this.watcher) this.watcher.config = this.config;
this.updateConfigUI('Gespeichert (DB/Local)');
}
}
loadConfig(file) {
if (!file) return;
this.configFileName = file.name;
const reader = new FileReader();
reader.onload = (e) => {
const text = e.target.result;
const ConfigParser = window.EDIBridge.ConfigParser;
this.config = ConfigParser.parse(text);
if (this.watcher) this.watcher.config = this.config;
localStorage.setItem('ediConfig', text);
this.updateConfigUI(file.name);
if (this.currentData) {
this.autoPopulateNADs();
this.renderEnrichmentTable();
}
};
reader.readAsText(file);
}
updateConfigUI(name) {
this.configStatus.innerHTML = 'Konfig: ' + name + ' ';
this.configStatus.classList.add('loaded');
const badge = document.getElementById('nadSourceBadge');
if (badge) {
badge.textContent = 'Aus Konfig';
badge.className = 'badge badge-config';
}
document.getElementById('btnSaveConfig').style.display = 'inline-flex';
this.renderMappingTable();
this.renderIdMapping711Table();
// Populate Own Customer Number
if (this.ownCustomerNumber && window.EDIBridge.ConfigParser) {
this.ownCustomerNumber.value = window.EDIBridge.ConfigParser.lookupGeneral(this.config, 'OWN_CUSTOMER_NUMBER', '');
}
if (window.lucide) lucide.createIcons();
}
async saveConfig() {
if (!this.config) return;
this.mergeUIToConfig();
const ConfigParser = window.EDIBridge.ConfigParser;
const text = ConfigParser.serialize(this.config);
// Save to LocalStorage array as fallback
localStorage.setItem('ediConfig', text);
// Save to Database (the main requirement)
if (window.electronAPI && window.electronAPI.dbInsertConfig) {
try {
await window.electronAPI.dbInsertConfig(text);
console.log("Config successfully saved to Database");
} catch (e) {
console.error("Failed to save config to DB:", e);
}
}
// Sync with watcher
if (this.watcher) this.watcher.config = this.config;
const blob = new Blob([text], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = this.configFileName || 'config.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
// Revoke after a short delay to ensure browser handled the click
setTimeout(() => URL.revokeObjectURL(url), 100);
}
saveToStorage() {
if (!this.config) {
// If no config object yet, create an empty one
const ConfigParser = window.EDIBridge.ConfigParser;
this.config = { __sectionMap__: {} };
this.updateConfigUI('Gespeichert (Local)');
}
this.mergeUIToConfig();
const ConfigParser = window.EDIBridge.ConfigParser;
const text = ConfigParser.serialize(this.config);
localStorage.setItem('ediConfig', text);
console.log("Config auto-saved to LocalStorage");
if (this.watcher) this.watcher.config = this.config;
}
mergeUIToConfig() {
const ConfigParser = window.EDIBridge.ConfigParser;
const updateSection = (sectionName, selector, dataKey) => {
const section = ConfigParser.getSection(this.config, sectionName) || ConfigParser.ensureSection(this.config, sectionName);
document.querySelectorAll(selector).forEach(input => {
const val = input.value.trim();
const key = input.dataset[dataKey];
if (val) {
section[key] = val;
} else {
delete section[key];
}
});
};
// Sync General Settings
if (this.ownCustomerNumber) {
const genSection = ConfigParser.ensureSection(this.config, 'GENERAL');
const ownCust = this.ownCustomerNumber.value.trim();
if (ownCust) genSection['OWN_CUSTOMER_NUMBER'] = ownCust;
else delete genSection['OWN_CUSTOMER_NUMBER'];
}
updateSection('PAC_WEIGHT', '.pac-weight-input', 'pacmat');
updateSection('PAC_LENGTH', '.pac-ln-input', 'pacmat');
updateSection('PAC_WIDTH', '.pac-wd-input', 'pacmat');
updateSection('PAC_HEIGHT', '.pac-ht-input', 'pacmat');
updateSection('LIN_WEIGHT', '.lin-weight-input', 'suppmat');
// Sync NAD fields back to config
if (this.activeSourceId || this.activeTargetId) {
const quals = ['by', 'se', 'st', 'sf'];
const idMap = { 'by': this.activeTargetId, 'st': this.activeTargetId, 'se': this.activeSourceId, 'sf': this.activeSourceId };
const fields = {
'id': 'PARTY',
'qual': 'QUAL',
'name': 'NAME',
'name2': 'NAME2',
'street': 'STREET',
'city': 'CITY',
'zip': 'POSTCODE',
'country': 'COUNTRY'
};
quals.forEach(q => {
const plantId = idMap[q];
if (!plantId) return;
Object.entries(fields).forEach(([f, sectionSuffix]) => {
const el = document.getElementById(`nad-${q}-${f}`);
if (el) {
const val = el.value.trim();
const sectionName = `NAD_${q.toUpperCase()}_${sectionSuffix}`;
const section = ConfigParser.ensureSection(this.config, sectionName);
if (val) section[plantId] = val;
else delete section[plantId];
}
});
});
}
}
autoPopulateNADs() {
if (!this.config || !this.currentData) return;
const ConfigParser = window.EDIBridge.ConfigParser;
const h = this.currentData.interchanges[0]?.header;
if (!h) return;
this.activeTargetId = h.targetId;
this.activeSourceId = h.sourceId;
const targetId = h.targetId;
const sourceId = h.sourceId;
const by = ConfigParser.lookupNAD(this.config, 'BY', targetId);
if (by.name) this.fillNAD('by', by);
const se = ConfigParser.lookupNAD(this.config, 'SE', sourceId);
if (se.name) this.fillNAD('se', se);
const st = ConfigParser.lookupNAD(this.config, 'ST', targetId);
if (st.name) this.fillNAD('st', st);
const sf = ConfigParser.lookupNAD(this.config, 'SF', sourceId);
if (sf.name) this.fillNAD('sf', sf);
}
fillNAD(prefix, data) {
const set = (field, val) => {
const el = document.getElementById('nad-' + prefix + '-' + field);
if (el) el.value = val || '';
};
set('id', data.id);
set('qual', data.qual);
set('name', data.name);
set('name2', data.name2);
set('street', data.street);
set('city', data.city);
set('zip', data.zip);
set('country', data.country);
}
// ─── Mode Toggle ─────────────────────────────────────────────────
setMode(mode) {
this.mode = mode;
const modes = ['outbound-bosch', 'outbound-zf', 'outbound-ifm', 'inbound-bosch', 'inbound-ifm', 'inbound-invrpt', 'validate-edifact'];
const btnIds = ['btnOutboundBosch', 'btnOutboundZf', 'btnOutboundIfm', 'btnInboundBosch', 'btnInboundIfm', 'btnInboundInvrpt', 'btnValidateEdifact'];
modes.forEach((m, i) => {
const btn = document.getElementById(btnIds[i]);
if (btn) btn.classList.toggle('active', m === mode);
});
// Update drop zone text
const labelMap = {
'outbound-bosch': 'VDA 4913 Datei auswählen → Bosch DESADV',
'outbound-zf': 'VDA 4913 Datei auswählen → ZF DESADV',
'outbound-ifm': 'VDA 4913 Datei auswählen → IFM DELVRY03',
'inbound-bosch': 'EDIFACT DELFOR Datei auswählen → VDA 4905',
'inbound-ifm': 'IFM DELFOR D04A Datei auswählen → VDA 4905',
'inbound-invrpt': 'INVRPT Datei auswählen → VDA 4913 EDL36',
'validate-edifact': 'EDIFACT Datei auswählen zur reinen Validierung/Prüfung',
};
if (this.dropText) this.dropText.textContent = labelMap[mode] || '';
// Hide enrichment/result when switching modes
if (this.enrichmentSection) this.enrichmentSection.style.display = 'none';
if (this.resultSection) this.resultSection.style.display = 'none';
}
// ─── File Handling ───────────────────────────────────────────────
handleFile(file) {
if (!file) return;
this.currentFilename = file.name;
const reader = new FileReader();
reader.onload = (e) => {
this.rawContent = e.target.result;
this._processContent(this.rawContent);
};
reader.readAsText(file);
}
_processContent(content) {
const trimmed = content.trim();
// Check if we should auto-detect based on customer ID
const VDAParser = window.EDIBridge.VDAParser;
let detectedMode = null;
if (VDAParser) {
let customerId = VDAParser.getCustomerId(trimmed);
// If not VDA, try EDIFACT/DELFOR
if (!customerId && window.EDIBridge.DelforParser) {
customerId = window.EDIBridge.DelforParser.getCustomerId(trimmed);
}
console.log("Detected Customer ID:", customerId);
if (customerId) {
const ConfigParser = window.EDIBridge.ConfigParser;
detectedMode = ConfigParser.lookupCustomerMode(this.config, customerId);
console.log("Mapped mode for ID", customerId, ":", detectedMode);
if (detectedMode) {
this.setMode(detectedMode);
}
}
}
switch (this.mode) {
case 'outbound-bosch':
this.processOutboundBosch(trimmed);
break;
case 'outbound-zf':
this.processOutboundZf(trimmed);
break;
case 'outbound-ifm':
this.processOutboundIfm(trimmed);
break;
case 'inbound-bosch':
this.processInboundBosch(trimmed);
break;
case 'inbound-ifm':
this.processInboundIfm(trimmed);
break;
case 'inbound-invrpt':
this.processInboundInvrpt(trimmed);
break;
case 'validate-edifact':
this.processValidateEdifact(trimmed);
break;
default:
// Auto-detect format fallback
const upper = trimmed.toUpperCase();
if (upper.includes('INVRPT')) {
this.setMode('inbound-invrpt');
this.processInboundInvrpt(trimmed);
} else if (upper.startsWith('UNA') || upper.startsWith('UNB')) {
this.setMode('inbound-bosch');
this.processInboundBosch(trimmed);
} else {
this.setMode('outbound-bosch');
this.processOutboundBosch(trimmed);
}
}
}
// ─── Inbound: Bosch DELFOR → VDA 4905 ────────────────────────────
processInboundBosch(content) {
this.statsGrid.innerHTML = '';
this.enrichmentSection.style.display = 'none';
this.currentData = null;
try {
const parser = window.EDIBridge.DelforParser;
const generator = window.EDIBridge.VDA4905Generator;
if (!parser || !generator) throw new Error('Inbound-Module (DelforParser/VDA4905Generator) nicht geladen.');
const parsed = parser.parse(content);
const vda4905 = generator.generate(parsed);
this.previewArea.innerText = vda4905;
this.resultSection.style.display = 'block';
document.getElementById('resultTitle').innerText = 'VDA 4905 Ergebnis (DELFOR → VDA 4905)';
const statsHtml = `
${parsed.segments.length}
EDIFACT Segments
${vda4905.split('\n').filter(l => l.trim()).length}
VDA 4905 Records
`;
this.statsGrid.innerHTML = statsHtml;
this.resultSection.scrollIntoView({ behavior: 'smooth' });
} catch (e) {
alert('Inbound Error (Bosch): ' + e.message);
console.error(e);
}
}
// ─── Inbound: IFM DELFOR D04A → VDA 4905 ────────────────────────
processInboundIfm(content) {
this.statsGrid.innerHTML = '';
this.enrichmentSection.style.display = 'none';
this.currentData = null;
try {
const Converter = window.EDIBridge.DelforToVDA4905Converter;
if (!Converter) throw new Error('IFM DELFOR Converter (DelforToVDA4905Converter) nicht geladen.');
const result = Converter.convertWithValidation(content);
if (result.errors && result.errors.length > 0) {
console.warn('IFM DELFOR Validation Warnings:', result.errors);
}
const vda4905 = result.output || result;
const outputText = typeof vda4905 === 'string' ? vda4905 : JSON.stringify(vda4905, null, 2);
this.previewArea.innerText = outputText;
this.resultSection.style.display = 'block';
document.getElementById('resultTitle').innerText = 'VDA 4905 Ergebnis (IFM DELFOR D04A → VDA 4905)';
const lines = outputText.split('\n').filter(l => l.trim()).length;
const statsHtml = `
${lines}
VDA 4905 Records
`;
this.statsGrid.innerHTML = statsHtml;
if (result.warnings && result.warnings.length > 0) {
this.statsGrid.innerHTML += `
${result.warnings.length}
Warnungen
`;
}
this.resultSection.scrollIntoView({ behavior: 'smooth' });
} catch (e) {
alert('Inbound Error (IFM): ' + e.message);
console.error(e);
}
}
// ─── Inbound: INVRPT D13A → VDA 4913 ────────────────────────
processInboundInvrpt(content) {
this.statsGrid.innerHTML = '';
this.enrichmentSection.style.display = 'none';
this.currentData = null;
try {
const Converter = window.EDIBridge.InvrptToVDA4913;
if (!Converter) throw new Error('INVRPT-Module (InvrptToVDA4913) nicht geladen.');
const vda4913 = Converter.convert(content, this.config);
this.previewArea.innerText = vda4913;
this.resultSection.style.display = 'block';
document.getElementById('resultTitle').innerText = 'VDA 4913 Ergebnis (INVRPT → VDA 4913)';
const statsHtml = `
${vda4913.split('\\n').filter(l => l.trim()).length}
VDA 4913 Records
`;
this.statsGrid.innerHTML = statsHtml;
this.resultSection.scrollIntoView({ behavior: 'smooth' });
} catch (e) {
alert('Inbound Error (INVRPT): ' + e.message);
console.error(e);
}
}
// ─── Utility: Validate EDIFACT (ts-edifact) ─────────────────────
async processValidateEdifact(content) {
this.statsGrid.innerHTML = '';
this.enrichmentSection.style.display = 'none';
this.currentData = null;
if (!window.electronAPI || !window.electronAPI.validateEdifact) {
alert('Validierung (ts-edifact) ist nur in der Electron-App verfügbar.');
return;
}
try {
const result = await window.electronAPI.validateEdifact(content);
const parsed = await window.electronAPI.parseEdifactLib(content);
document.getElementById('resultTitle').innerText = 'EDIFACT Validierungsprozess';
let html = `
${result.valid ? ' Gültiges EDIFACT' : ' Ungültiges EDIFACT'}
`;
if (result.stats.messageType) {
html += `
Nachrichtentyp: ${result.stats.messageType} (Version: ${result.stats.messageVersion})
`;
}
if (result.stats.sender || result.stats.receiver) {
html += `
Sender: ${result.stats.sender || '-'} Empfänger: ${result.stats.receiver || '-'}
`;
}
if (result.errors && result.errors.length > 0) {
html += `
Fehler
${result.errors.map(e => `${e} `).join('')}
`;
}
if (result.warnings && result.warnings.length > 0) {
html += `
Warnungen
${result.warnings.map(e => `${e} `).join('')}
`;
}
html += `
Extrahierte Segmente (Rohdaten)
${JSON.stringify(parsed.segments, null, 2)}
`;
// Since previewArea usually escapes HTML via innerText, we set innerHTML and handle styling:
this.previewArea.innerHTML = html;
this.previewArea.style.whiteSpace = 'normal';
this.previewArea.style.backgroundColor = 'transparent';
this.previewArea.style.padding = '0';
this.resultSection.style.display = 'block';
const statsHtml = `
${result.valid ? 'OK' : 'ERR'}
Syntax-Status
${result.stats.segmentCount || 0}
Segmente
`;
this.statsGrid.innerHTML = statsHtml;
this.resultSection.scrollIntoView({ behavior: 'smooth' });
if (window.lucide) lucide.createIcons();
} catch (e) {
alert('Validierungsfehler: ' + e.message);
console.error(e);
}
}
// ─── Outbound: VDA 4913 → Bosch DESADV ──────────────────────────
processOutboundBosch(content) {
const VDAParser = window.EDIBridge.VDAParser;
const vda = VDAParser.parse(content);
if (vda.interchanges.length === 0) {
alert('Keine gültigen VDA 4913 Daten gefunden.');
return;
}
this.currentData = vda;
this.updateStats(vda);
this.autoPopulateNADs();
this.renderEnrichmentTable();
}
// ─── Outbound: VDA 4913 → ZF DESADV ──────────────────────────
processOutboundZf(content) {
const VDAParser = window.EDIBridge.VDAParser;
const vda = VDAParser.parse(content);
if (vda.interchanges.length === 0) {
alert('Keine gültigen VDA 4913 Daten gefunden.');
return;
}
this.currentData = vda;
this.updateStats(vda);
this.autoPopulateNADs();
this.renderEnrichmentTable();
}
// ─── Outbound: VDA 4913 → IFM DELVRY03 ──────────────────────────
processOutboundIfm(content) {
this.statsGrid.innerHTML = '';
this.enrichmentSection.style.display = 'none';
try {
const VDAParser = window.EDIBridge.VDAParser;
const Converter = window.EDIBridge.VDA4913ToDELVRY03;
if (!VDAParser) throw new Error('VDA Parser nicht geladen.');
if (!Converter) throw new Error('VDA4913ToDELVRY03 Converter nicht geladen.');
const parsed = VDAParser.parse(content);
if (!parsed.interchanges || parsed.interchanges.length === 0) {
alert('Keine gültigen VDA 4913 Daten gefunden.');
return;
}
const idocs = Converter.convert(parsed, this.config || {});
const xml = Converter.toXML(idocs);
this.previewArea.innerText = xml;
this.resultSection.style.display = 'block';
document.getElementById('resultTitle').innerText = 'IFM DELVRY03 Ergebnis (VDA 4913 → DELVRY03)';
const statsHtml = `
${parsed.interchanges.length}
Interchanges
${idocs.length}
IDocs erzeugt
`;
this.statsGrid.innerHTML = statsHtml;
this.resultSection.scrollIntoView({ behavior: 'smooth' });
} catch (e) {
alert('Outbound Error (IFM): ' + e.message);
console.error(e);
}
}
// ─── Stats ───────────────────────────────────────────────────────
updateStats(vda) {
if (!vda || !vda.interchanges) return;
const count = vda.interchanges.length;
const dnCount = vda.interchanges.reduce((acc, i) =>
acc + i.transports.reduce((tAcc, t) => tAcc + t.deliveryNotes.length, 0), 0);
this.statsGrid.innerHTML = `
${dnCount}
Delivery Notes
`;
}
// ─── Enrichment Table ────────────────────────────────────────────
renderEnrichmentTable() {
const tbody = document.querySelector('#enrichmentTable tbody');
if (!tbody || !this.currentData) return;
tbody.innerHTML = '';
const ConfigParser = window.EDIBridge.ConfigParser;
const positions = [];
this.currentData.interchanges.forEach(i => {
i.transports.forEach(t => {
t.deliveryNotes.forEach(dn => {
dn.positions.forEach(pos => positions.push(pos));
});
});
});
positions.forEach(pos => {
const row = document.createElement('tr');
const suppMat = pos.data.suppMat;
let weight = 0;
let status = 'Aus VDA ';
if (this.config) {
weight = ConfigParser.lookupLinWeight(this.config, suppMat);
if (weight > 0) {
status = 'Aus Konfig ';
}
}
row.innerHTML = `
${pos.data.custMat}
${suppMat}
${pos.data.qty}
${pos.data.unit}
${status}
`;
tbody.appendChild(row);
});
this.enrichmentSection.style.display = 'block';
this.renderPackagingTable();
}
renderPackagingTable() {
const tbody = document.querySelector('#packagingTable tbody');
if (!tbody || !this.currentData) return;
tbody.innerHTML = '';
const ConfigParser = window.EDIBridge.ConfigParser;
const seen = new Set();
const packages = [];
this.currentData.interchanges.forEach(i => {
i.transports.forEach(t => {
t.deliveryNotes.forEach(dn => {
dn.positions.forEach(pos => {
pos.packaging.forEach(p => {
const key = p.packMatSupp;
if (!seen.has(key)) {
seen.add(key);
packages.push(p);
}
});
});
});
});
});
packages.forEach(p => {
const row = document.createElement('tr');
const packMatSupp = p.packMatSupp;
let weight = null, ln = null, wd = null, ht = null;
let status = 'Default ';
if (this.config) {
const w = ConfigParser.lookupPacWeight(this.config, packMatSupp);
if (w > 0) weight = w;
const l = ConfigParser.lookupPacDimension(this.config, packMatSupp, 'LENGTH');
if (l > 0) ln = l;
const w_d = ConfigParser.lookupPacDimension(this.config, packMatSupp, 'WIDTH');
if (w_d > 0) wd = w_d;
const h = ConfigParser.lookupPacDimension(this.config, packMatSupp, 'HEIGHT');
if (h > 0) ht = h;
if (weight || ln || wd || ht) {
status = 'Aus Konfig ';
}
}
row.innerHTML = `
${p.packMatCust}
${packMatSupp}
${p.qty}
${status}
`;
tbody.appendChild(row);
});
}
// ─── Customer Mapping Editor ─────────────────────────────────────
renderMappingTable() {
if (!this.config) return;
const ConfigParser = window.EDIBridge.ConfigParser;
const section = ConfigParser.getSection(this.config, 'CUSTOMER_MAPPING') || {};
const tbody = document.querySelector('#mappingTable tbody');
if (!tbody) return;
tbody.innerHTML = '';
const formatMap = {
'outbound-bosch': 'VDA 4913 → Bosch DESADV',
'outbound-zf': 'VDA 4913 → ZF DESADV',
'outbound-ifm': 'VDA 4913 → IFM DELVRY03',
'inbound-bosch': 'DELFOR → VDA 4905',
'inbound-ifm': 'IFM DELFOR → VDA 4905',
'inbound-invrpt': 'INVRPT → VDA 4913',
'validate-edifact': 'EDIFACT Validieren'
};
for (const [id, mode] of Object.entries(section)) {
const row = document.createElement('tr');
row.innerHTML = `
${id}
${formatMap[mode] || mode}
`;
tbody.appendChild(row);
}
if (window.lucide) lucide.createIcons();
}
addMapping() {
if (!this.config) {
alert('Bitte zuerst eine Konfiguration laden!');
return;
}
const idInput = document.getElementById('newMappingId');
const modeInput = document.getElementById('newMappingMode');
const id = idInput.value.trim();
const mode = modeInput.value;
if (!id) {
alert('Bitte eine Kundennummer eingeben.');
return;
}
const ConfigParser = window.EDIBridge.ConfigParser;
const section = ConfigParser.ensureSection(this.config, 'CUSTOMER_MAPPING');
section[id] = mode;
idInput.value = '';
this.renderMappingTable();
this.saveToStorage();
// Update UI status to show it's active but maybe needs file export
if (this.configStatus.classList.contains('loaded')) {
// Keep "loaded" but maybe change icon temporarily or just keep it
} else {
this.updateConfigUI('Gespeichert (Local)');
}
if (window.lucide) lucide.createIcons();
}
removeMapping(id) {
if (!this.config) return;
const ConfigParser = window.EDIBridge.ConfigParser;
const section = ConfigParser.getSection(this.config, 'CUSTOMER_MAPPING');
if (section && section[id]) {
delete section[id];
this.renderMappingTable();
this.saveToStorage();
if (window.lucide) lucide.createIcons();
}
}
// ─── VDA 711 ID Mapping Editor ───────────────────────────────────
renderIdMapping711Table() {
if (!this.config) return;
const ConfigParser = window.EDIBridge.ConfigParser;
const section = ConfigParser.getSection(this.config, 'VDA711_ID_MAPPING') || {};
const tbody = document.querySelector('#idMapping711Table tbody');
if (!tbody) return;
tbody.innerHTML = '';
for (const [oldId, newId] of Object.entries(section)) {
const row = document.createElement('tr');
row.innerHTML = `
${oldId}
${newId}
`;
tbody.appendChild(row);
}
if (window.lucide) lucide.createIcons();
}
addIdMapping711() {
if (!this.config) {
alert('Bitte zuerst eine Konfiguration laden!');
return;
}
const oldInput = document.getElementById('newIdMapOriginal');
const newInput = document.getElementById('newIdMapTarget');
const oldId = oldInput.value.trim();
const newId = newInput.value.trim();
if (!oldId || !newId) {
alert('Bitte sowohl die alte als auch die neue ID eingeben.');
return;
}
const ConfigParser = window.EDIBridge.ConfigParser;
const section = ConfigParser.ensureSection(this.config, 'VDA711_ID_MAPPING');
section[oldId] = newId;
oldInput.value = '';
newInput.value = '';
this.renderIdMapping711Table();
this.saveToStorage();
}
removeIdMapping711(id) {
if (!this.config) return;
const ConfigParser = window.EDIBridge.ConfigParser;
const section = ConfigParser.getSection(this.config, 'VDA711_ID_MAPPING');
if (section && section[id]) {
delete section[id];
this.renderIdMapping711Table();
this.saveToStorage();
}
}
// ─── Collect Data ────────────────────────────────────────────────
collectNADData() {
const readNAD = (prefix) => ({
id: document.getElementById(`nad-${prefix}-id`).value.trim(),
qual: document.getElementById(`nad-${prefix}-qual`).value.trim(),
name: document.getElementById(`nad-${prefix}-name`).value.trim(),
name2: document.getElementById(`nad-${prefix}-name2`).value.trim(),
street: document.getElementById(`nad-${prefix}-street`).value.trim(),
city: document.getElementById(`nad-${prefix}-city`).value.trim(),
zip: document.getElementById(`nad-${prefix}-zip`).value.trim(),
country: document.getElementById(`nad-${prefix}-country`).value.trim()
});
return { BY: readNAD('by'), SE: readNAD('se'), ST: readNAD('st'), SF: readNAD('sf') };
}
collectWeights() {
const linWeights = {};
document.querySelectorAll('.lin-weight-input').forEach(input => {
const val = parseFloat(input.value);
if (val) linWeights[input.dataset.suppmat] = val;
});
const pacWeights = {};
document.querySelectorAll('.pac-weight-input').forEach(input => {
const val = parseFloat(input.value);
if (val) pacWeights[input.dataset.pacmat] = val;
});
const pacDims = {};
document.querySelectorAll('.pac-ln-input').forEach(input => {
const key = input.dataset.pacmat;
const dims = pacDims[key] || { ln: 0, wd: 0, ht: 0 };
dims.ln = parseFloat(input.value) || dims.ln; pacDims[key] = dims;
});
document.querySelectorAll('.pac-wd-input').forEach(input => {
const key = input.dataset.pacmat;
const dims = pacDims[key] || { ln: 0, wd: 0, ht: 0 };
dims.wd = parseFloat(input.value) || dims.wd; pacDims[key] = dims;
});
document.querySelectorAll('.pac-ht-input').forEach(input => {
const key = input.dataset.pacmat;
const dims = pacDims[key] || { ln: 0, wd: 0, ht: 0 };
dims.ht = parseFloat(input.value) || dims.ht; pacDims[key] = dims;
});
return { linWeights, pacWeights, pacDims };
}
// ─── Generate & Download ─────────────────────────────────────────
generateOutput() {
if (this.mode === 'outbound-bosch') {
const VDA4913ToDESADV = window.EDIBridge.VDA4913ToDESADV;
if (!VDA4913ToDESADV) return;
const nadData = this.collectNADData();
const weights = this.collectWeights();
const edifact = VDA4913ToDESADV.convert(this.rawContent, nadData, weights, this.config);
this.previewArea.innerText = edifact;
this.resultSection.style.display = 'block';
document.getElementById('resultTitle').innerText = 'Bosch DESADV Ergebnis';
this.resultSection.scrollIntoView({ behavior: 'smooth' });
} else if (this.mode === 'outbound-zf') {
const VDA4913ToDESADVZF = window.EDIBridge.VDA4913ToDESADVZF;
if (!VDA4913ToDESADVZF) {
console.error("VDA4913ToDESADVZF is not loaded");
return;
}
const nadData = this.collectNADData();
const weights = this.collectWeights();
const edifact = VDA4913ToDESADVZF.convert(this.rawContent, nadData, weights, this.config);
this.previewArea.innerText = edifact;
this.resultSection.style.display = 'block';
document.getElementById('resultTitle').innerText = 'ZF DESADV Ergebnis';
this.resultSection.scrollIntoView({ behavior: 'smooth' });
} else if (this.mode === 'outbound-ifm') {
// Already processed in processOutboundIfm
this.processOutboundIfm(this.rawContent);
}
}
downloadResult() {
const text = this.previewArea.innerText;
if (!text) return;
const blob = new Blob([text], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
let base = this.currentFilename || 'output';
const lastDot = base.lastIndexOf('.');
if (lastDot !== -1) base = base.substring(0, lastDot);
const extMap = {
'outbound-bosch': '.edi',
'outbound-zf': '.edi',
'outbound-ifm': '.delvry',
'inbound-bosch': '.vda',
'inbound-ifm': '.vda',
'inbound-invrpt': '.vda',
};
a.download = base + (extMap[this.mode] || '.edi');
a.click();
URL.revokeObjectURL(url);
}
viewResult() {
const text = this.previewArea.innerText;
if (!text) return;
this.showPage('viewer');
if (this.viewer) {
this.viewer.processContent(text);
}
}
editResult() {
const text = this.previewArea.innerText;
if (!text) return;
this.showPage('editor');
if (this.editor) {
this.editor.fileName = this.currentFilename || 'output.edi';
this.editor.processContent(text);
}
}
/**
* Log a conversion to the SQLite history database.
* @param {Object} opts
*/
async _logConversion(opts) {
if (!window.electronAPI || !window.electronAPI.dbInsert) return;
try {
await window.electronAPI.dbInsert({
dateiname: opts.dateiname || this.currentFilename || 'unbekannt',
ausgangsdateiname: opts.ausgangsdateiname || null,
quellformat: opts.quellformat || null,
zielformat: opts.zielformat || null,
konvertierungsmodus: opts.konvertierungsmodus || this.mode,
kunden_id: opts.kunden_id || null,
eingang_daten: opts.eingang_daten || null,
ausgang_daten: opts.ausgang_daten || null,
status: opts.status || 'ERFOLGREICH',
fehlermeldung: opts.fehlermeldung || null,
quelle: opts.quelle || 'MANUELL'
});
} catch (e) {
console.warn('[DB] Log error:', e);
}
}
// ─── Settings: Folder Selection ──────────────────────────────────
async selectInputFolder() {
if (!this.watcher) return;
const folder = await this.watcher.selectFolder();
if (folder) {
document.getElementById('inputDirPath').value = folder;
this.watcher.saveSettings({
inputDir: folder,
outputDir: document.getElementById('outputDirPath').value
});
}
}
async selectOutputFolder() {
if (!this.watcher) return;
const folder = await this.watcher.selectFolder();
if (folder) {
document.getElementById('outputDirPath').value = folder;
this.watcher.saveSettings({
inputDir: document.getElementById('inputDirPath').value,
outputDir: folder
});
}
}
async selectInvrptOutputFolder() {
if (!this.watcher) return;
const folder = await this.watcher.selectFolder();
if (folder) {
document.getElementById('invrptOutputPath').value = folder;
this.watcher.saveSettings({ invrptOutputDir: folder });
}
}
// ─── Settings: Watcher Controls ──────────────────────────────────
async startWatcher() {
if (!this.watcher) return;
const inputDir = document.getElementById('inputDirPath').value;
const outputDir = document.getElementById('outputDirPath').value;
const invrptOutputDir = document.getElementById('invrptOutputPath').value;
if (!inputDir || !outputDir) {
alert('Bitte beide Ordner (Input & Output) angeben.');
return;
}
const result = await this.watcher.start(inputDir, outputDir, invrptOutputDir);
if (result.error) {
alert('Watcher-Fehler: ' + result.error);
}
}
async stopWatcher() {
if (this.watcher) await this.watcher.stop();
}
async pauseWatcher() {
if (this.watcher) await this.watcher.pause();
}
async resumeWatcher() {
if (this.watcher) await this.watcher.resume();
}
// ─── Settings: Outbound Watcher Controls ──────────────────────────
async selectOutboundInput() {
if (!this.outboundWatcher) return;
const folder = await window.electronAPI.selectFolder();
if (folder) {
document.getElementById('outboundInputPath').value = folder;
window.electronAPI.saveSettings({
outboundInputDir: folder
});
}
}
async selectOutboundOutput() {
if (!this.outboundWatcher) return;
const folder = await window.electronAPI.selectFolder();
if (folder) {
document.getElementById('outboundOutputPath').value = folder;
window.electronAPI.saveSettings({
outboundOutputDir: folder
});
}
}
async startOutboundWatcher() {
if (!this.outboundWatcher) return;
const inPath = document.getElementById('outboundInputPath').value;
const outPath = document.getElementById('outboundOutputPath').value;
if (!inPath || !outPath) {
alert('Bitte beide Pfade für ausgehende Dateien angeben.');
return;
}
await this.outboundWatcher.start(inPath, outPath);
}
async stopOutboundWatcher() {
if (this.outboundWatcher) await this.outboundWatcher.stop();
}
async pauseOutboundWatcher() {
if (this.outboundWatcher) await this.outboundWatcher.pause();
}
async resumeOutboundWatcher() {
if (this.outboundWatcher) await this.outboundWatcher.resume();
}
updateOutboundWatcherUI(status) {
const dot = document.getElementById('outboundWatcherDot');
const text = document.getElementById('outboundWatcherStatusText');
const btnStart = document.getElementById('btnStartOutboundWatcher');
const btnPause = document.getElementById('btnPauseOutboundWatcher');
const btnResume = document.getElementById('btnResumeOutboundWatcher');
const btnStop = document.getElementById('btnStopOutboundWatcher');
dot.className = 'watcher-status-dot';
switch (status) {
case 'running':
dot.classList.add('running');
text.textContent = 'Aktiv – Läuft';
btnStart.disabled = true;
btnPause.disabled = false;
btnPause.style.display = '';
btnResume.style.display = 'none';
btnStop.disabled = false;
break;
case 'paused':
dot.classList.add('paused');
text.textContent = 'Pausiert';
btnStart.disabled = true;
btnPause.style.display = 'none';
btnResume.style.display = '';
btnStop.disabled = false;
break;
case 'stopped':
default:
dot.classList.add('stopped');
text.textContent = 'Gestoppt';
btnStart.disabled = false;
btnPause.disabled = true;
btnPause.style.display = '';
btnResume.style.display = 'none';
btnStop.disabled = true;
break;
}
if (window.lucide) try { lucide.createIcons(); } catch (e) { }
}
// ─── Settings: Werksnummer Watcher Controls ──────────────────────
async selectWerksInput() {
if (!this.werksWatcher) return;
const folder = await window.electronAPI.selectFolder();
if (folder) {
document.getElementById('werksInputPath').value = folder;
window.electronAPI.saveSettings({
werksInputDir: folder
});
}
}
async selectWerksOutput() {
if (!this.werksWatcher) return;
const folder = await window.electronAPI.selectFolder();
if (folder) {
document.getElementById('werksOutputPath').value = folder;
window.electronAPI.saveSettings({
werksOutputDir: folder
});
}
}
async startWerksWatcher() {
if (!this.werksWatcher) return;
const inPath = document.getElementById('werksInputPath').value;
const outPath = document.getElementById('werksOutputPath').value;
if (!inPath || !outPath) {
alert('Bitte beide Werks-Pfade angeben.');
return;
}
await this.werksWatcher.start(inPath, outPath);
}
async stopWerksWatcher() {
if (this.werksWatcher) await this.werksWatcher.stop();
}
async pauseWerksWatcher() {
if (this.werksWatcher) await this.werksWatcher.pause();
}
async resumeWerksWatcher() {
if (this.werksWatcher) await this.werksWatcher.resume();
}
updateWerksWatcherUI(status) {
const dot = document.getElementById('werksWatcherDot');
const text = document.getElementById('werksWatcherStatusText');
const btnStart = document.getElementById('btnStartWerksWatcher');
const btnPause = document.getElementById('btnPauseWerksWatcher');
const btnResume = document.getElementById('btnResumeWerksWatcher');
const btnStop = document.getElementById('btnStopWerksWatcher');
dot.className = 'watcher-status-dot';
switch (status) {
case 'running':
dot.classList.add('running');
text.textContent = 'Aktiv – Läuft';
btnStart.disabled = true;
btnPause.disabled = false;
btnPause.style.display = '';
btnResume.style.display = 'none';
btnStop.disabled = false;
break;
case 'paused':
dot.classList.add('paused');
text.textContent = 'Pausiert';
btnStart.disabled = true;
btnPause.style.display = 'none';
btnResume.style.display = '';
btnStop.disabled = false;
break;
case 'stopped':
default:
dot.classList.add('stopped');
text.textContent = 'Gestoppt';
btnStart.disabled = false;
btnPause.disabled = true;
btnPause.style.display = '';
btnResume.style.display = 'none';
btnStop.disabled = true;
break;
}
if (window.lucide) try { lucide.createIcons(); } catch (e) { }
}
updateWatcherUI(status) {
const dot = document.getElementById('watcherDot');
const text = document.getElementById('watcherStatusText');
const btnStart = document.getElementById('btnStartWatcher');
const btnPause = document.getElementById('btnPauseWatcher');
const btnResume = document.getElementById('btnResumeWatcher');
const btnStop = document.getElementById('btnStopWatcher');
// Reset
dot.className = 'watcher-status-dot';
switch (status) {
case 'running':
dot.classList.add('running');
text.textContent = 'Aktiv – Überwachung läuft';
btnStart.disabled = true;
btnPause.disabled = false;
btnPause.style.display = '';
btnResume.style.display = 'none';
btnStop.disabled = false;
break;
case 'paused':
dot.classList.add('paused');
text.textContent = 'Pausiert';
btnStart.disabled = true;
btnPause.style.display = 'none';
btnResume.style.display = '';
btnStop.disabled = false;
break;
case 'stopped':
default:
dot.classList.add('stopped');
text.textContent = 'Gestoppt';
btnStart.disabled = false;
btnPause.disabled = true;
btnPause.style.display = '';
btnResume.style.display = 'none';
btnStop.disabled = true;
break;
}
if (window.lucide) try { lucide.createIcons(); } catch (e) { }
}
// ─── Log ─────────────────────────────────────────────────────────
appendLogEntry(entry) {
const container = document.getElementById('logContainer');
if (!container) return;
// Remove empty message if present
const empty = container.querySelector('.log-empty');
if (empty) empty.remove();
const el = document.createElement('div');
el.className = 'log-entry log-' + entry.level;
el.innerHTML = `
${entry.time}
${entry.level.toUpperCase()}
${entry.fileName ? `${entry.fileName} ` : ''}
${entry.message}
`;
container.prepend(el);
}
clearLog() {
const container = document.getElementById('logContainer');
if (container) {
container.innerHTML = 'Noch keine Aktivitäten.
';
}
if (this.watcher) this.watcher.log = [];
}
}
window.EDIBridge.App = App;