(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.
293 lines
11 KiB
JavaScript
293 lines
11 KiB
JavaScript
'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,'&').replace(/</g,'<').replace(/>/g,'>')}</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,'&').replace(/</g,'<').replace(/>/g,'>');
|
||
return `<code style="word-break:break-all">${esc}</code>`;
|
||
}
|
||
if (type === 'text') {
|
||
return value.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||
}
|
||
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';
|
||
});
|
||
|
||
})();
|