Files
vda-to-edifact-converter/js/history.js
2026-03-13 09:53:40 +01:00

414 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* HistoryManager Frontend for conversion history
* Renders paginated table, detail view, stats, and filters.
*/
window.EDIBridge = window.EDIBridge || {};
class HistoryManager {
constructor(app) {
this.app = app;
this.currentPage = 1;
this.pageSize = 20;
this.statusFilter = 'ALL';
this.dateFilter = 'ALL';
this.searchTerm = '';
this.totalRows = 0;
}
/**
* Called when the History page becomes visible.
*/
async activate() {
await this.loadStats();
await this.loadTable();
}
/**
* Load and render statistics cards.
*/
async loadStats() {
if (!window.electronAPI) return;
const stats = await window.electronAPI.dbGetStats();
const container = document.getElementById('historyStats');
if (!container) return;
container.innerHTML = `
<div class="stat-card ${this.statusFilter === 'ALL' && this.dateFilter === 'ALL' ? 'active' : ''}"
onclick="app.historyManager.setFilter('ALL')" style="cursor:pointer;">
<div class="stat-value">${stats.total}</div>
<div class="stat-label">Gesamt</div>
</div>
<div class="stat-card ${this.statusFilter === 'ERFOLGREICH' ? 'active' : ''}"
onclick="app.historyManager.setFilter('ERFOLGREICH')" style="cursor:pointer; border-color: var(--success);">
<div class="stat-value" style="color: var(--success);">${stats.success}</div>
<div class="stat-label">Erfolgreich</div>
</div>
<div class="stat-card ${this.statusFilter === 'FEHLERHAFT' ? 'active' : ''}"
onclick="app.historyManager.setFilter('FEHLERHAFT')" style="cursor:pointer; border-color: var(--danger);">
<div class="stat-value" style="color: var(--danger);">${stats.error}</div>
<div class="stat-label">Fehlerhaft</div>
</div>
<div class="stat-card ${this.dateFilter === 'TODAY' ? 'active' : ''}"
onclick="app.historyManager.setDateFilter('TODAY')" style="cursor:pointer;">
<div class="stat-value" style="color: var(--accent);">${stats.today}</div>
<div class="stat-label">Heute</div>
</div>
`;
}
/**
* Load and render the conversion table.
*/
async loadTable() {
if (!window.electronAPI) return;
const offset = (this.currentPage - 1) * this.pageSize;
const params = {
limit: this.pageSize,
offset,
statusFilter: this.statusFilter,
dateFilter: this.dateFilter,
search: this.searchTerm
};
console.log('[History] Loading table with params:', params);
const result = await window.electronAPI.dbGetAll(params);
this.totalRows = result.total;
const totalPages = Math.max(1, Math.ceil(result.total / this.pageSize));
const tbody = document.getElementById('historyTableBody');
const pagination = document.getElementById('historyPagination');
if (!tbody) return;
if (result.rows.length === 0) {
tbody.innerHTML = `<tr><td colspan="6" style="text-align:center; padding:40px; color: var(--text-dim); font-style:italic;">Keine Einträge gefunden.</td></tr>`;
} else {
tbody.innerHTML = result.rows.map(row => {
const ts = this._formatTimestamp(row.zeitstempel);
const statusIcon = row.status === 'ERFOLGREICH'
? '<span style="color: var(--success);">✅</span>'
: '<span style="color: var(--danger);">❌</span>';
const modus = this._shortModus(row.konvertierungsmodus, row.quellformat, row.zielformat);
const source = row.quelle === 'WATCHER'
? '<span class="badge badge-config" style="font-size:0.65rem;">AUTO</span>'
: '<span class="badge badge-edi" style="font-size:0.65rem;">GUI</span>';
return `
<tr class="history-row" onclick="app.historyManager.showDetail(${row.id})" style="cursor:pointer;">
<td style="color: var(--text-dim); font-size:0.8rem;">${row.id}</td>
<td>${ts}</td>
<td>
<div style="font-weight:600;">${this._escapeHtml(row.dateiname)}</div>
${row.ausgangsdateiname ? `<div style="font-size:0.75rem; color:var(--text-dim);">→ ${this._escapeHtml(row.ausgangsdateiname)}</div>` : ''}
</td>
<td>${modus}</td>
<td>${statusIcon} ${source}</td>
<td>
<button class="btn-secondary" style="padding:4px 8px; font-size:0.7rem;"
onclick="event.stopPropagation(); app.historyManager.deleteEntry(${row.id})">
<i data-lucide="trash-2" style="width:12px;height:12px;"></i>
</button>
</td>
</tr>
`;
}).join('');
}
if (pagination) {
pagination.innerHTML = `
<button class="btn-secondary" ${this.currentPage <= 1 ? 'disabled' : ''}
onclick="app.historyManager.goToPage(${this.currentPage - 1})">
<i data-lucide="chevron-left" style="width:14px;height:14px;"></i> Zurück
</button>
<span style="color: var(--text-dim); font-size:0.85rem;">
Seite ${this.currentPage} von ${totalPages} (${result.total} Einträge)
</span>
<button class="btn-secondary" ${this.currentPage >= totalPages ? 'disabled' : ''}
onclick="app.historyManager.goToPage(${this.currentPage + 1})">
Weiter <i data-lucide="chevron-right" style="width:14px;height:14px;"></i>
</button>
`;
}
// Re-render icons
if (window.lucide) {
try { lucide.createIcons(); } catch (e) { }
}
}
/**
* Show detail view for a single conversion.
*/
async showDetail(id) {
if (!window.electronAPI) return;
const record = await window.electronAPI.dbGetById(id);
if (!record) return;
const modal = document.getElementById('historyDetailModal');
const content = document.getElementById('historyDetailContent');
if (!modal || !content) return;
const statusBadge = record.status === 'ERFOLGREICH'
? '<span style="color:var(--success); font-weight:700;">✅ ERFOLGREICH</span>'
: '<span style="color:var(--danger); font-weight:700;">❌ FEHLERHAFT</span>';
content.innerHTML = `
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
<h3 style="margin:0; font-size:1.1rem;">Konvertierung #${record.id}</h3>
<button class="btn-secondary" onclick="app.historyManager.closeDetail()">
<i data-lucide="x" style="width:14px;height:14px;"></i> Schließen
</button>
</div>
<div class="history-detail-grid">
<div class="history-detail-item">
<span class="label">Zeitstempel</span>
<strong>${this._formatTimestamp(record.zeitstempel)}</strong>
</div>
<div class="history-detail-item">
<span class="label">Status</span>
${statusBadge}
</div>
<div class="history-detail-item">
<span class="label">Eingangsdatei</span>
<strong>${this._escapeHtml(record.dateiname)}</strong>
</div>
<div class="history-detail-item">
<span class="label">Ausgangsdatei</span>
<strong>${this._escapeHtml(record.ausgangsdateiname || '-')}</strong>
</div>
<div class="history-detail-item">
<span class="label">Quellformat</span>
<strong>${this._escapeHtml(record.quellformat || '-')}</strong>
</div>
<div class="history-detail-item">
<span class="label">Zielformat</span>
<strong>${this._escapeHtml(record.zielformat || '-')}</strong>
</div>
<div class="history-detail-item">
<span class="label">Konvertierungsmodus</span>
<strong>${this._escapeHtml(record.konvertierungsmodus || '-')}</strong>
</div>
<div class="history-detail-item">
<span class="label">Kunden-ID</span>
<strong>${this._escapeHtml(record.kunden_id || '-')}</strong>
</div>
<div class="history-detail-item">
<span class="label">Quelle</span>
<strong>${record.quelle === 'WATCHER' ? '🤖 Automatisch (Watcher)' : '👤 Manuell (GUI)'}</strong>
</div>
</div>
${record.fehlermeldung ? `
<div style="background: rgba(239,68,68,0.1); border:1px solid rgba(239,68,68,0.3); border-radius:8px; padding:12px; margin:15px 0; color: #fca5a5;">
<strong>Fehlermeldung:</strong><br>
${this._escapeHtml(record.fehlermeldung)}
</div>
` : ''}
<div class="history-detail-tabs">
<button class="btn-secondary history-tab-btn active" data-tab="eingang" onclick="app.historyManager.switchTab('eingang')">
📄 Eingangsdaten
</button>
<button class="btn-secondary history-tab-btn" data-tab="ausgang" onclick="app.historyManager.switchTab('ausgang')">
📄 Ausgangsdaten
</button>
${record.ausgang_daten ? `
<button class="btn-secondary" onclick="app.historyManager.openInViewer(${record.id})">
<i data-lucide="eye" style="width:14px;height:14px;"></i> Im Viewer öffnen
</button>
` : ''}
</div>
<div id="historyTabEingang" class="history-tab-content">
<pre style="max-height:400px; overflow:auto; font-size:0.8rem;">${this._escapeHtml(record.eingang_daten || 'Keine Eingangsdaten gespeichert.')}</pre>
</div>
<div id="historyTabAusgang" class="history-tab-content" style="display:none;">
<pre style="max-height:400px; overflow:auto; font-size:0.8rem;">${this._escapeHtml(record.ausgang_daten || 'Keine Ausgangsdaten gespeichert.')}</pre>
</div>
`;
modal.style.display = 'flex';
if (window.lucide) {
try { lucide.createIcons(); } catch (e) { }
}
}
switchTab(tab) {
const eingang = document.getElementById('historyTabEingang');
const ausgang = document.getElementById('historyTabAusgang');
const buttons = document.querySelectorAll('.history-tab-btn');
buttons.forEach(btn => {
btn.classList.toggle('active', btn.dataset.tab === tab);
});
if (tab === 'eingang') {
if (eingang) eingang.style.display = 'block';
if (ausgang) ausgang.style.display = 'none';
} else {
if (eingang) eingang.style.display = 'none';
if (ausgang) ausgang.style.display = 'block';
}
}
async openInViewer(id) {
const record = await window.electronAPI.dbGetById(id);
if (!record || !record.ausgang_daten) return;
this.closeDetail();
// Switch to viewer page and process the content
if (this.app) {
this.app.showPage('viewer');
if (this.app.viewer) {
this.app.viewer.processContent(record.ausgang_daten);
}
}
}
closeDetail() {
const modal = document.getElementById('historyDetailModal');
if (modal) modal.style.display = 'none';
}
async deleteEntry(id) {
if (!confirm('Eintrag wirklich löschen?')) return;
await window.electronAPI.dbDelete(id);
await this.loadStats();
await this.loadTable();
}
goToPage(page) {
if (page < 1) return;
this.currentPage = page;
this.loadTable();
}
setFilter(status) {
this.statusFilter = status;
this.dateFilter = 'ALL';
this.searchTerm = ''; // Clear search when picking a card
this.currentPage = 1;
// Reset search input
const searchInput = document.getElementById('historySearch');
if (searchInput) searchInput.value = '';
// Update active button state
document.querySelectorAll('.history-filter-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.filter === status);
});
this.loadStats();
this.loadTable();
}
setDateFilter(dateType) {
this.dateFilter = dateType;
this.statusFilter = 'ALL'; // Reset status when picking date
this.searchTerm = ''; // Clear search when picking a card
this.currentPage = 1;
// Reset filter bar buttons
document.querySelectorAll('.history-filter-btn').forEach(btn => {
btn.classList.toggle('active', false);
});
// Reset search input
const searchInput = document.getElementById('historySearch');
if (searchInput) searchInput.value = '';
this.loadStats();
this.loadTable();
}
onSearch(value) {
this.searchTerm = value;
this.currentPage = 1;
// Debounce
clearTimeout(this._searchTimer);
this._searchTimer = setTimeout(() => this.loadTable(), 300);
}
async exportCSV() {
if (!window.electronAPI) return;
// Fetch all rows (no pagination)
const result = await window.electronAPI.dbGetAll({
limit: 99999,
offset: 0,
statusFilter: this.statusFilter,
search: this.searchTerm
});
if (!result.rows || result.rows.length === 0) {
alert('Keine Daten zum Exportieren.');
return;
}
const headers = ['ID', 'Zeitstempel', 'Dateiname', 'Ausgangsdatei', 'Quellformat', 'Zielformat', 'Modus', 'Kunden-ID', 'Status', 'Fehlermeldung', 'Quelle'];
const csvRows = [headers.join(';')];
result.rows.forEach(row => {
csvRows.push([
row.id,
row.zeitstempel,
`"${(row.dateiname || '').replace(/"/g, '""')}"`,
`"${(row.ausgangsdateiname || '').replace(/"/g, '""')}"`,
row.quellformat || '',
row.zielformat || '',
row.konvertierungsmodus || '',
row.kunden_id || '',
row.status,
`"${(row.fehlermeldung || '').replace(/"/g, '""')}"`,
row.quelle
].join(';'));
});
const csv = csvRows.join('\n');
const blob = new Blob(['\ufeff' + csv], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `konvertierungen_${new Date().toISOString().slice(0, 10)}.csv`;
a.click();
URL.revokeObjectURL(url);
}
// ─── Helpers ─────────────────────────────────────────────────────
_formatTimestamp(ts) {
if (!ts) return '-';
try {
const d = new Date(ts.replace(' ', 'T'));
return d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })
+ ' ' + d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
} catch (e) {
return ts;
}
}
_shortModus(modus, quellformat, zielformat) {
if (quellformat && zielformat) {
// Shorten for display
const qf = quellformat.replace('EDIFACT ', '').replace('D04A', '').trim();
const zf = zielformat.replace('EDIFACT ', '').trim();
return `<span style="font-size:0.8rem;">${this._escapeHtml(qf)}${this._escapeHtml(zf)}</span>`;
}
if (modus) return `<span style="font-size:0.8rem;">${this._escapeHtml(modus)}</span>`;
return '<span style="color:var(--text-dim);">-</span>';
}
_escapeHtml(str) {
if (!str) return '';
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
}
window.EDIBridge.HistoryManager = HistoryManager;