Steuerkram/public/static/js/bankimport.js
fds2610-bot 23efce3196 Fix #161: Restposten aus Tech-Debt-Analyse (O-CHAT.html, fints.php/bankimport.php, Request-Klasse)
(a) public/O-CHAT.html nach BEISPIELE/ verschoben, Test 93 hält den Webroot sauber.
(b) bank/fints.php in Teil-Views + public/static/js/fints.js aufgeteilt.
(c) settings/bankimport.php in Teil-Views + public/static/js/bankimport.js aufgeteilt.
(d) src/Request.php angelegt, BankController als erster Controller umgestellt.
2026-08-18 13:12:12 +02:00

293 lines
11 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.

'use strict';
(function () {
const DATE_FORMAT_OPTIONS = JSON.parse(document.getElementById('bankimport-data').dataset.dateFormatOptions);
function badgeTextColor(hex) {
const r = parseInt(hex.slice(1,3),16);
const g = parseInt(hex.slice(3,5),16);
const b = parseInt(hex.slice(5,7),16);
return (0.299*r + 0.587*g + 0.114*b) > 128 ? '#1e293b' : '#ffffff';
}
function updatePreview(colorInput, hex) {
const preview = colorInput.closest('div')?.querySelector('.bi-kuerzel-preview');
if (!preview) return;
preview.style.background = hex;
preview.style.color = badgeTextColor(hex);
}
// ── Hilfsfunktionen ──────────────────────────────────────────────
function getFormatId(cell) {
return cell.closest('[data-format-id]')?.dataset.formatId;
}
function showError(cell, msg) {
cell.classList.add('bi-cell--error');
let err = cell.parentElement.querySelector('.bi-error-msg');
if (!err) { err = document.createElement('span'); err.className = 'bi-error-msg'; cell.after(err); }
err.textContent = msg;
setTimeout(() => { cell.classList.remove('bi-cell--error'); err.remove(); }, 3000);
}
async function saveField(formatId, field, value) {
const body = new URLSearchParams({ field, value: value ?? '' });
const res = await fetch(`/settings/bankimport/${formatId}/field`, {
method: 'POST',
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString(),
});
return res.json();
}
function displayForCell(type, value, field) {
if (type === 'number' || type === 'nullint') {
if (value === '' || value === null || value === undefined) {
return '<span style="color:var(--color-muted);font-style:italic">–</span>';
}
return `<code>${value}</code>`;
}
if (type === 'char' || type === 'char0') {
if (value === '') return '<span style="color:var(--color-muted);font-style:italic">keines</span>';
return `<code>${value.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}</code>`;
}
if (type === 'dateformat') {
const example = DATE_FORMAT_OPTIONS[value] || '?';
return `<code>${value}</code><span style="color:var(--color-muted);margin-left:.4rem">→ ${example}</span>`;
}
if (type === 'template') {
if (!value) return '<span style="color:var(--color-muted);font-style:italic">nicht gesetzt</span>';
const esc = value.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
return `<code style="word-break:break-all">${esc}</code>`;
}
if (type === 'text') {
return value.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
return value;
}
// ── Klick-Handler für .bi-cell ───────────────────────────────────
document.addEventListener('click', (e) => {
const cell = e.target.closest('.bi-cell');
if (!cell || cell.querySelector('input,select')) return;
const field = cell.dataset.field;
const type = cell.dataset.type;
const rawValue = cell.dataset.value ?? '';
const formatId = getFormatId(cell);
if (!formatId) return;
const originalHTML = cell.innerHTML;
cell.innerHTML = '';
cell.classList.remove('bi-cell--error');
let el;
if (type === 'number' || type === 'nullint') {
el = document.createElement('input');
el.type = 'number';
el.min = cell.dataset.min ?? '0';
el.max = cell.dataset.max ?? '99';
el.value = rawValue;
} else if (type === 'char') {
el = document.createElement('input');
el.type = 'text';
el.maxLength = 1;
el.value = rawValue;
} else if (type === 'char0') {
el = document.createElement('input');
el.type = 'text';
el.maxLength = 1;
el.value = rawValue;
el.placeholder = '(leer)';
el.style.width = '70px';
} else if (type === 'dateformat') {
el = document.createElement('select');
for (const [fmt, example] of Object.entries(DATE_FORMAT_OPTIONS)) {
const opt = document.createElement('option');
opt.value = fmt;
opt.textContent = `${fmt} → ${example}`;
if (fmt === rawValue) opt.selected = true;
el.appendChild(opt);
}
} else if (type === 'template') {
el = document.createElement('input');
el.type = 'text';
el.value = rawValue;
el.style.width = '100%';
el.style.boxSizing = 'border-box';
} else {
el = document.createElement('input');
el.type = 'text';
el.value = rawValue;
el.style.width = '180px';
}
cell.appendChild(el);
el.focus();
if (el.tagName === 'INPUT' && el.type === 'text') el.select();
let done = false;
async function commit() {
if (done) return;
done = true;
const newVal = el.tagName === 'SELECT' ? el.value : el.value;
cell.classList.add('bi-cell--saving');
cell.innerHTML = '';
let json;
try {
json = await saveField(formatId, field, newVal);
} catch (_) {
json = { error: 'Verbindungsfehler.' };
}
cell.classList.remove('bi-cell--saving');
if (json.error) {
cell.innerHTML = originalHTML;
showError(cell, json.error);
return;
}
cell.dataset.value = newVal;
cell.innerHTML = displayForCell(type, newVal, field);
// Formatname im Card-Header synchronisieren
if (field === 'name') {
const title = cell.closest('.card')?.querySelector('.card-title');
if (title) title.dataset.value = newVal;
}
// Kürzel-Vorschau-Badge aktualisieren
if (field === 'kuerzel') {
const preview = cell.closest('div')?.querySelector('.bi-kuerzel-preview');
if (preview) preview.textContent = newVal;
}
}
function cancel() {
if (done) return;
done = true;
cell.innerHTML = originalHTML;
}
if (el.tagName === 'SELECT') {
el.addEventListener('change', commit);
el.addEventListener('keydown', (ev) => { if (ev.key === 'Escape') { ev.stopPropagation(); cancel(); } });
el.addEventListener('blur', () => { if (!done) commit(); });
} else {
el.addEventListener('blur', commit);
el.addEventListener('keydown', (ev) => {
if (ev.key === 'Enter') { ev.preventDefault(); el.blur(); }
if (ev.key === 'Escape') { ev.preventDefault(); cancel(); }
});
}
});
// ── Colorpicker für farbe ────────────────────────────────────────
document.addEventListener('input', (e) => {
const ci = e.target.closest('.bi-color');
if (ci) updatePreview(ci, ci.value);
});
document.addEventListener('change', async (e) => {
const ci = e.target.closest('.bi-color');
if (!ci) return;
const formatId = getFormatId(ci);
const field = ci.dataset.field;
const value = ci.value;
ci.disabled = true;
let json;
try { json = await saveField(formatId, field, value); }
catch (_) { json = { error: 'Verbindungsfehler.' }; }
ci.disabled = false;
if (json.error) {
showError(ci, json.error);
} else {
updatePreview(ci, value);
}
});
// ── Format löschen ──────────────────────────────────────────────
document.addEventListener('click', async (e) => {
const btn = e.target.closest('.bi-delete-format');
if (!btn) return;
const formatId = btn.dataset.formatId;
const formatName = btn.dataset.formatName;
if (!confirm(`Format „${formatName}" wirklich löschen?`)) return;
btn.disabled = true;
let json;
try {
const res = await fetch(`/settings/bankimport/${formatId}/delete`, { method: 'POST' });
json = await res.json();
} catch (_) {
json = { error: 'Verbindungsfehler.' };
}
if (json.error) {
alert(json.error);
btn.disabled = false;
return;
}
btn.closest('.card').remove();
});
// ── Vorschlags-Card verwerfen ────────────────────────────────────
const dismissBtn = document.getElementById('suggestion-dismiss');
if (dismissBtn) {
dismissBtn.addEventListener('click', async () => {
dismissBtn.disabled = true;
try {
await fetch('/settings/bankimport/dismiss-suggestion', { method: 'POST' });
} catch (_) {}
const card = document.getElementById('suggestion-card');
if (card) card.remove();
});
}
// ── Checkboxen für has_header_row ────────────────────────────────
document.addEventListener('change', async (e) => {
const cb = e.target.closest('.bi-checkbox');
if (!cb) return;
const label = cb.closest('.bi-checkbox-label');
const textSpan = label?.querySelector('.bi-checkbox-text');
const field = cb.dataset.field;
const formatId = getFormatId(cb);
if (!formatId) return;
const newVal = cb.checked ? '1' : '0';
cb.disabled = true;
let json;
try {
json = await saveField(formatId, field, newVal);
} catch (_) {
json = { error: 'Verbindungsfehler.' };
}
cb.disabled = false;
if (json.error) {
cb.checked = !cb.checked; // Revert
if (textSpan) textSpan.textContent = cb.checked ? 'Ja' : 'Nein';
alert(json.error);
return;
}
if (textSpan) textSpan.textContent = cb.checked ? 'Ja' : 'Nein';
});
})();