
| Current Path : /home/cgabriel/ |
Linux ift1.ift-informatik.de 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 |
| Current File : //home/cgabriel/b2.html |
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Buchhaltung</title>
<link href="https://unpkg.com/tabulator-tables@5.5.2/dist/css/tabulator.min.css" rel="stylesheet">
<script src="https://unpkg.com/tabulator-tables@5.5.2/dist/js/tabulator.min.js"></script>
<style>
body { font-family: monospace; margin: 0; }
#topbar { padding: 0.5rem; background: #eee; display: flex; align-items: center; gap: 1rem; }
#mandanten-buttons-container { display: flex; gap: 1rem; padding: 0.5rem; flex-wrap: wrap; }
.mandant-column { display: flex; flex-direction: column; }
.mandant-buttons { display: flex; flex-direction: column; gap: 0.25rem; }
.mandant-button-wrapper { position: relative; margin-bottom: 0.25rem; }
.mandant-button { width: 120px; height: 30px; position: relative; padding-right: 20px; box-sizing: border-box; cursor: pointer; }
.mandant-button.active { background-color: #007bff; color: white; }
.mandant-button .close-x { position: absolute; right: 4px; top: 50%; transform: translateY(-50%); background: transparent; border: none; color: inherit; font-weight: bold; cursor: pointer; padding: 0; }
select { max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#mandanten-content-container { padding: 0.5rem; border-top: 1px solid #ccc; }
.meta { display: flex; gap: 0.5rem; margin-bottom: 0.5rem; }
.meta input { flex: 1; }
pre.baum { background: #fff; border: 1px solid #ccc; padding: 0.5rem; white-space: pre-wrap; margin-top: 0.5rem; }
#satellite { position:fixed; top:10%; left:20%; width:60%; height:70%; background:#fff; border:1px solid #000; display:none; padding:10px; overflow:auto; }
.cell-konto { padding-left: 6px; padding-right: 6px; }
/* Durchgehende Linien */
.tabulator .tabulator-row { border-bottom: 1px solid #ccc; }
.tabulator .tabulator-tableHolder { border-bottom: none; }
/* Tabelle nimmt ganze Seitenbreite */
.tabulator { width: 100%; }
/* Alle Zellen 14px monospace */
.tabulator .tabulator-cell {
font-family: monospace;
font-size: 14px;
line-height: normal;
}
/* Editor monospace gelb */
.tabulator input.tabulator-edit-input,
.tabulator textarea.tabulator-edit-input {
background-color: #ffff00 !important;
font-family: monospace !important;
font-size: 14px !important;
padding: 0 3px;
box-sizing: border-box;
line-height: normal;
}
/* Bearbeitete Zeilen hellgelb */
.tabulator .tabulator-row.modified { background-color: #fff9b0 !important; }
/* Flag-Spalte hellgrau */
.flag-cell {
background-color: #e0e0e0;
font-weight: bold;
text-align: center;
}
#loginbar input { font-family: monospace; font-size: 14px; }
#login-msg { color: #b00000; font-weight: bold; }
</style>
</head>
<body>
<div id="topbar">
<div id="loginbar" style="display:flex; gap:0.5rem; align-items:center;">
<div style="display:flex; gap:0.5rem; align-items:center;">
<label></label>
<select id="mandant-select" onchange="addMandantInstance(this.value)" {% if not logged_in %}disabled{% endif %}>
<option value="">Mandant wählen...</option>
{% for m in mandanten %}
<option value="{{ m }}">{{ m }}</option>
{% endfor %}
</select>
</div>
{% if logged_in %}
<span>Login: <b>{{ username }}</b></span>
<button onclick="doLogout()">Logout</button>
{% else %}
<input id="login-customer" placeholder="Customer" autocomplete="customer">
<input id="login-user" placeholder="Username" autocomplete="username">
<input id="login-pass" placeholder="Passwort" type="password" autocomplete="current-password">
<button onclick="doLogin()">Login</button>
<span id="login-msg"></span>
{% endif %}
</div>
</div>
<div id="mandanten-buttons-container"></div>
<div id="mandanten-content-container"></div>
<script>
const mandantInstances = [];
let activeInstance = null;
let xid = null;
let currentFile = null;
let currentDir = '';
let monthOffset = 0;
let AnchorRow = null;
window.copyBuffer = [];
/***************************************************************************/
async function doLogin(){
const cus = document.getElementById("login-customer").value.trim();
const u = document.getElementById("login-user").value.trim();
const p = document.getElementById("login-pass").value;
const msg = document.getElementById("login-msg");
msg.textContent = "";
try{
const r = await fetch("/api/login", {
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({customer:cus, username:u, password:p})
});
if(!r.ok){
msg.textContent = "Login fehlgeschlagen";
return;
// } else {
// sessionStorage.setItem("cus",cus);
}
location.reload();
}catch(e){
msg.textContent = "Netzwerkfehler";
}
}
/***************************************************************************/
async function doLogout(){
try{
await fetch("/api/logout", {method:"POST"});
}catch(e){}
location.reload();
}
/***************************************************************************/
// ------------------ Hilfsfunktionen ------------------
function measureText(text, font="14px monospace") {
const canvas = measureText._canvas || (measureText._canvas = document.createElement("canvas"));
const ctx = canvas.getContext("2d");
ctx.font = font;
return ctx.measureText(text).width;
}
/***************************************************************************/
// Berechnet Flag-Spalte 0/1
function computeFlag(row, needle){
if(!needle) return "1";
const terms = needle.toLowerCase().split(";").map(t=>t.trim()).filter(t=>t);
for(const v of Object.values(row)){
if(!v) continue;
if(terms.some(t=>String(v).toLowerCase().includes(t))) return "1";
}
return "0";
}
/***************************************************************************/
// Update Flags & Sortierung
function updateFlags(){
if(!activeInstance || !activeInstance.tabulator) return;
const needle = document.querySelector(".trigger")?.value || "";
activeInstance.tabulator.getRows().forEach(row=>{
row.update({
_flag: computeFlag(row.getData(), needle)
});
});
// Sortiere absteigend nach _flag
activeInstance.tabulator.setSort("_flag","desc");
}
/***************************************************************************/
// ------------------ Mandantenlogik ------------------
function addMandantInstance(baseId) {
if (!baseId) return;
// const cus = sessionStorage.getItem("cus")
let instanceNo = 0;
const used = mandantInstances.filter(i => i.baseId===baseId).map(i=>i.instanceNo);
while(used.includes(instanceNo)) instanceNo++;
const label = instanceNo===0 ? baseId : `${baseId} (${instanceNo})`;
const instance = { baseId, instanceNo, label, context:{subdir:'',ktofile:'', data:null }, tabulator:null, tabulatorDiv:null };
mandantInstances.push(instance);
renderMandantButtons();
setActiveInstance(instance);
document.getElementById("mandant-select").value = '';
}
/***************************************************************************/
function renderMandantButtons() {
const container = document.getElementById("mandanten-buttons-container");
container.innerHTML = '';
const grouped = {};
mandantInstances.forEach(inst => {
if (!grouped[inst.baseId]) grouped[inst.baseId] = [];
grouped[inst.baseId].push(inst);
});
for (const [baseId, instances] of Object.entries(grouped)) {
const col = document.createElement("div");
col.className = "mandant-column";
const buttonsDiv = document.createElement("div");
buttonsDiv.className = "mandant-buttons";
instances.forEach(inst => {
const wrapper = document.createElement("div");
wrapper.className = "mandant-button-wrapper";
const btn = document.createElement('button');
btn.textContent = inst.label;
btn.className = "mandant-button";
if (inst === activeInstance) btn.classList.add('active');
btn.onclick = () => setActiveInstance(inst);
const del = document.createElement('button');
del.textContent = '×';
del.className = 'close-x';
del.onclick = (e) => { e.stopPropagation(); deleteMandantInstance(inst); };
btn.appendChild(del);
wrapper.appendChild(btn);
buttonsDiv.appendChild(wrapper);
});
col.appendChild(buttonsDiv);
container.appendChild(col);
}
}
/***************************************************************************/
function deleteMandantInstance(inst) {
const idx = mandantInstances.indexOf(inst);
if (idx !== -1) mandantInstances.splice(idx,1);
if (activeInstance === inst) {
activeInstance = mandantInstances[0] || null;
if (activeInstance) reloadContextInstance(activeInstance);
else document.getElementById('mandanten-content-container').innerHTML = '';
}
renderMandantButtons();
}
/*function setActiveInstance(inst) {
activeInstance = inst;
renderMandantButtons();
reloadContextInstance(inst);
}
*/
/***************************************************************************/
function setActiveInstance(inst) {
if (activeInstance) {
activeInstance.tabulatorDiv = document.getElementById(xid);
// console.log("ACT",xid,activeInstance);
}
activeInstance = inst;
renderMandantButtons();
const container = document.getElementById('mandanten-content-container');
container.innerHTML = '';
console.log(inst);
if (inst.tabulatorDiv) {
container.appendChild(inst.tabulatorDiv)
} else {
reloadContextInstance(inst);
}
xid = "tabs-"+inst.baseId+"-"+String(inst.instanceNo);
}
function reloadContextInstance(inst) {
if (!inst) inst = activeInstance;
fetch(`/api/context?mandant=${inst.baseId}&subdir=${inst.context.subdir}&kpattern=${inst.context.ktofile}`)
.then(r => r.json())
.then(data => {
inst.context.data = data;
renderActiveContent(inst);
});
}
// ------------------ Tabelle rendern ------------------
function renderActiveContent(inst) {
const ctx = inst.context.data;
if (!ctx) return;
const container = document.getElementById('mandanten-content-container');
const triggerValue = ctx.active.trigger || "";
if (ctx.active.buchungen) {
ctx.active.buchungen = ctx.active.buchungen.map(b => {
b.bemerkung = (b.bemerkung||"").trim().replace(/\n{2,}/g,"\n");
return b;
});
}
const ddd = ctx.files['.kto.html'] || [];
const dd1 = ddd[0];
container.innerHTML = `
<div id="tabs-${inst.baseId}-${inst.instanceNo}">
<select id="kto-select" class="kto-select" onchange="changeKtoFile(this.value, '', '${inst.baseId}')">
<option>${dd1}</option>
${(ctx.files['.kto.html']||[])
.map(f=>`<option ${f===ctx.active.kto?'selected':''}>${f}</option>`).join('')}
</select>
<select class="dir-select" onchange="onDirSelect(activeInstance, this, '${inst.baseId}')">
<option value="" selected>DIR</option>
${(ctx.files['._d_']||[])
.map(f=>`<option ${f===ctx.active.kto?'selected':''}>${f}</option>`).join('')}
</select>
<select class="file-select" onchange="openFileWindow(activeInstance, this)">
<option value="" selected>PDF</option>
${(ctx.files['.pdf']||[])
.map(f=>`<option ${f===ctx.active.kto?'selected':''}>${f}</option>`).join('')}
</select>
<select class="file-select" onchange="openFileWindow(activeInstance, this)">
<option value="" selected>CSV</option>
${(ctx.files['.csv']||[])
.map(f=>`<option ${f===ctx.active.kto?'selected':''}>${f}</option>`).join('')}
</select>
<select class="file-select" onchange="openFileWindow(activeInstance, this)">
<option value="" selected>MISC</option>
${(ctx.files['.xxx']||[])
.map(f=>`<option ${f===ctx.active.kto?'selected':''}>${f}</option>`).join('')}
</select>
<!--select id="dir-select" onchange="openDirectorySatellite(this.value)"></select>
<select id="pdf-select" onchange="openFileSatellite(this.value,'.pdf')"></select>
<select id="txt-select" onchange="openFileSatellite(this.value,'.txt')"></select-->
<input type="file" id="upload-input" />
<button id="uploadbutton" onclick="uploadFile()">Upload</button>
${inst.context.subdir}
<div id="satellite">
<button onclick="downloadCurrent()">Download</button>
<pre id="file-view"></pre>
<button onclick="deleteCurrent()">Delete</button>
<input id="rename-input" placeholder="new name" />
<button onclick="renameCurrent()">Save</button>
<button onclick="closeSatellite()">Close</button>
</div>
<div class="meta">
<!--input class="konto" value="${ctx.active.konto||''}" placeholder="Konto">
<input class="hash" value="${ctx.active.hash||''}" placeholder="Hash" disabled>
<input class="saldo" value="${ctx.active.saldo||''}" placeholder="Saldo" disabled-->
<!--input class="triggxx" value="${triggerValue}" placeholder="Triggerbegriff" onchange="updateFlags()"-->
</div>
<div id="tabulator-${inst.baseId}-${inst.instanceNo}" class="tabulator"></div>
<div id="table-toolbar" style="display:flex; gap:0.5rem; margin-top:0.5rem;">
<button onclick="activeInstance.tabulatorDiv='';reloadContextInstance(activeInstance)">Reload</button>
<input class="konto" value="${ctx.active.konto||''}" placeholder="Konto">
<input class="hash" value="${ctx.active.hash||''}" placeholder="Hash" disabled>
<input class="saldo" value="${ctx.active.saldo||''}" placeholder="Saldo" disabled>
<button onclick="sendToDB(activeInstance)">Speichern</button>
<input class="rename" value="${triggerValue}" placeholder="Neuer Name" style="flex:1;">
<input class="trigger" value="${triggerValue}" placeholder="Filter" onchange="updateFlags()" style="flex:1;">
<input class="search" id="searchText" placeholder="Suchen" style="flex:1;">
<input class="replace" id="replaceText" placeholder="Ersetzen" style="flex:1;">
<button onclick="replaceInTable(activeInstance)">Ersetzen</button>
</div>
<pre class="baum">${ctx.active.baum.join('\n')}</pre>
</div>
`;
const tabDiv = document.getElementById(`tabulator-${inst.baseId}-${inst.instanceNo}`);
const rowHeight = 35;
document.getElementById("uploadbutton").disabled = true; // (ctx.wmode == "0");
function markModified(cell){
cell.getRow().getElement().classList.add("modified");
cell.getRow().update({_flag: computeFlag(cell.getRow().getData(), document.querySelector(".trigger").value)});
updateFlags();
}
const columnsDef = [
{title:'', field:'lfdnr', visible: false },
{title:"", field:"_flag", width:30, hozAlign:"center", frozen:true, cssClass:"flag-cell",
formatter:function(cell){
const val = computeFlag(cell.getRow().getData(), document.querySelector(".trigger").value);
cell.getElement().style.backgroundColor = val === "1" ? "#d6ffd6" : "#ffd6d6";
return val;
}
},
{title:'Datum', field:'datum', editor:"input",
editorParams:{elementAttributes:{style:"font-family:monospace;font-size:14px;background-color:#ffff00;width:100%;box-sizing:border-box;"}},
cellEdited: markModified},
{title:'Betrag', field:'betrag', width:110, editor:"input", hozAlign:'right',
formatter:function(cell){
const val = parseFloat(cell.getValue());
cell.getElement().style.backgroundColor = (!isNaN(val) && val<0) ? "#ffd6d6" : "#e6ffe6";
return cell.getValue();
},
editorParams:{elementAttributes:{style:"font-family:monospace;text-align:right;font-size:14px;background-color:#ffff00;width:100%;box-sizing:border-box;"}},
cellEdited: markModified
},
{title:'Konto1', field:'konto1', editor:"input", cssClass:'cell-konto',
editorParams:{elementAttributes:{style:"font-family:monospace;font-size:14px;background-color:#ffff00;width:100%;box-sizing:border-box;"}},
cellEdited: markModified},
{title:'Konto2', field:'konto2', editor:"input", cssClass:'cell-konto',
editorParams:{elementAttributes:{style:"font-family:monospace;font-size:14px;background-color:#ffff00;width:100%;box-sizing:border-box;"}},
cellEdited: markModified},
{title:'Saldo', field:'saldo', width:120, editor:"input", hozAlign:'right',
formatter:function(cell){
const val = parseFloat(cell.getValue());
cell.getElement().style.backgroundColor = (!isNaN(val) && val<0) ? "#ffd6d6" : "#e6f0ff";
return cell.getValue();
},
editorParams:{elementAttributes:{style:"font-family:monospace;text-align:right;font-size:14px;background-color:#ffff00;width:100%;box-sizing:border-box;"}},
cellEdited: markModified
},
{title:'Bemerkung', field:'bemerkung', editor:"input", widthGrow:100,
editorParams:{elementAttributes:{style:"font-family:monospace;font-size:14px;background-color:#ffff00;width:100%;box-sizing:border-box;"}},
cellEdited: markModified}
];
inst.tabulator = new Tabulator(tabDiv, {
height: Math.min((ctx.active.buchungen.length*rowHeight)+40,600),
layout:"fitDataStretch",
movableColumns:true,
resizableColumns:true,
selectable:true,
// selectableRangeMode:"click",
// selectableRange:false,
// selectablePersistence:false,
selectableRangeMode:"click",
selectableRange:"true",
selectableRangeColumns:"false",
selectableRangeRows:"true",
// rowClick:function(e,row){lastFocusedRow=row;},
// cellClick:function(e,cell){lastFocusedRow=cell.getRow();},
// cellFocused:function(cell){alert("OKI");console.log("OKKK");inst.lastFocusedRow=cell.getRow();},
columns:columnsDef,
data: ctx.active.buchungen || []
});
inst.lastFocusedRow=null;
// inst.tabulator.on("cellFocused",function(cell){alert("OKK");inst.lastFocusedRow=cell.getRow();});
inst.tabulator.on("rowMouseEnter",function(e,row){inst.lastFocusedRow=row;});
document.addEventListener("keydown", function(e){
if(e.key == "Delete") {
if(!activeInstance || !activeInstance.tabulator) return;
const table = activeInstance.tabulator;
const focusRow = activeInstance.lastFocusedRow;
const selected = table.getSelectedRows();
if(!focusRow){
table.deselectRow();
return;
}
const inside = selected.includes(focusRow);
if(inside){
let aktNextRow = null;
let z1 = 0;
selected.forEach(r =>{z1=z1+1;aktNextRow=r.getNextRow();r.delete()});
if (aktNextRow && (z1<2)) {
aktNextRow.select();
activeInstance.lastFocusedRow = aktNextRow;
setTimeout(()=>{
const el = aktNextRow.getElement();
if(el){
el.dispatchEvent(new MouseEvent("click",{bubbles:true}));
}
},4);
}
}else{
table.deselectRow();
}
return;
}
const table = activeInstance.tabulator;
// ---------- COPY ----------
if(e.ctrlKey && (e.key === "c" || e.key === "C")){
e.preventDefault();
copySelection(table);
monthOffset = 0;
}
// ---------- PASTE ----------
if(e.ctrlKey && (e.key === "v" || e.key === "V")){
e.preventDefault();
pasteSelection(table);
}
// ---------- +1 MONTH PASTE ----------
if(e.ctrlKey && (e.key === "m" || e.key === "M")){
e.preventDefault();
monthOffset = monthOffset + 1;
pasteSelection(table);
}
});
// Initial Flags berechnen und sortieren
const ktoselect = document.getElementById("kto-select");
if (ktoselect != null) { ktoselect.options[0].text = inst.context.ktofile || file; }
updateFlags();
}
/***************************************************************************/
function changeKtoFile(selectElem1, selectElem2, mandant) {
const inst = activeInstance
const ctx = inst.context.data;
const file = selectElem1;
let dir = inst.context.subdir + selectElem2;
if (selectElem2 !== "" ) { dir = dir + "/"; }
if (selectElem2 == "..") { dir = inst.context.data.ppar; }
fetch(`/api/context?mandant=${mandant}&subdir=${encodeURIComponent(dir)}&kpattern=${encodeURIComponent(file)}`)
.then(r => r.json())
.then(data => {
const inst = activeInstance;
inst.context.data = data;
inst.context.ktofile = file;
inst.context.subdir = dir;
renderActiveContent(inst);
});
ctx.active.konto = file;
}
/***************************************************************************/
function openFileWindow(actInst, file){
mandant = actInst.baseId;
subdir = actInst.context.subdir;
wmode = actInst.context.data.wmode;
const w = 900;
const h = 700;
// Position des aktuellen Browserfensters
const dualLeft = window.screenLeft !== undefined ? window.screenLeft : window.screenX;
const dualTop = window.screenTop !== undefined ? window.screenTop : window.screenY;
// Größe des aktuellen Browserfensters
const width = window.innerWidth || document.documentElement.clientWidth || screen.width;
const height = window.innerHeight || document.documentElement.clientHeight || screen.height;
const left = dualLeft + (width - w) / 2;
const top = dualTop + (height - h) / 2;
console.log("EEE",wmode);
window.open(
`/satellite?rw=${encodeURIComponent(wmode)}&mandant=${encodeURIComponent(mandant)}` +
`&subdir=${encodeURIComponent(subdir)}&file=${encodeURIComponent(file.value)}`,
"_blank",
`width=${w},height=${h},left=${left},top=${top},resizable=yes,scrollbars=yes`
);
const ctx = actInst.context.data;
ctx.files[".kto.html"][0] = file;
// file.blur();
// file.selectedIndex = 0;
// file.value=""
}
/***************************************************************************/
function openFileSatellite(name, ext){
currentFile = name;
fetch(`/api/file_view?rw=1?file=${encodeURIComponent(name)}?subdir=${encodeURIComponent(activeInstance.context.subdir)}?mandant=${encodeURIComponent(activeInstance.baseId)}`)
.then(r=>r.text())
.then(t=>{
document.getElementById('file-view').textContent = t;
document.getElementById('satellite').style.display='block';
});
}
/***************************************************************************/
function openDirectorySatellite(dir){
currentDir = dir;
document.getElementById('satellite').style.display='block';
}
/***************************************************************************/
function closeSatellite(){
document.getElementById('satellite').style.display='none';
}
/***************************************************************************/
function deleteCurrent(){
fetch('/api/file_delete',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({file:currentFile})})
.then(()=>closeSatellite());
}
/***************************************************************************/
function renameCurrent(){
const newname = document.getElementById('rename-input').value;
fetch('/api/file_rename',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({file:currentFile,newname})})
.then(()=>closeSatellite());
}
/***************************************************************************/
function downloadCurrent(){
window.location = `/api/file_download?file=${encodeURIComponent(currentFile)}`;
}
/***************************************************************************/
function uploadFile(){
const f = document.getElementById('upload-input').files[0];
const fd = new FormData();
fd.append('file',f);
fd.append('filename',f.name);
fd.append('subdir',activeInstance.context.subdir);
fd.append('mandant',activeInstance.baseId);
console.log("uploadFileff");
fetch('/api/file_upload',{method:'POST',body:fd}); // .then(()=>location.reload());
reloadContextInstance(activeInstance);
}
/***************************************************************************/
function onDirSelect(actInst, sel, mandant){
const v = sel.value;
// nur bei "." Satellite öffnen
if(v === "."){
openDirectoryWindow(actInst); // oder openDirectoryWindow(...) falls du schon eins hast
sel.selectedIndex = 0; // wichtig: zurücksetzen, damit "." wieder auswählbar ist
return;
}
// sonst: ganz normal Verzeichnis wechseln
changeKtoFile("", v, mandant);
// optional: zurücksetzen, damit man denselben Eintrag nochmal triggern kann
// sel.selectedIndex = 0;
}
/***************************************************************************/
function openDirectoryWindow(actInst){
const mandant = actInst.baseId;
const subdir = actInst.context.subdir;
const wmode = actInst.context.data.wmode;
const w = 700;
const h = 500;
const dualLeft = window.screenLeft !== undefined ? window.screenLeft : window.screenX;
const dualTop = window.screenTop !== undefined ? window.screenTop : window.screenY;
const width = window.innerWidth || document.documentElement.clientWidth || screen.width;
const height = window.innerHeight || document.documentElement.clientHeight || screen.height;
const left = dualLeft + (width - w) / 2;
const top = dualTop + (height - h) / 2;
window.open(
`/satellite?mode=dir&rw=${encodeURIComponent(wmode)}&mandant=${encodeURIComponent(mandant)}&subdir=${encodeURIComponent(subdir)}`,
"_blank",
`width=${w},height=${h},left=${left},top=${top},resizable=yes,scrollbars=yes`
);
}
/***************************************************************************/
function replaceInTable(actInst) {
let search = document.getElementById("searchText").value;
let replace = document.getElementById("replaceText").value;
let rows = actInst.tabulator.getRows();
rows.forEach(row => {
let data = row.getData();
if (data.bemerkung && data.bemerkung.includes(search)) {
data.bemerkung = data.bemerkung.replaceAll(search, replace);
row.update(data);
}
if (data.konto1 && data.konto1.includes(search)) {
data.konto1 = data.konto1.replaceAll(search, replace);
row.update(data);
}
if (data.konto2 && data.konto2.includes(search)) {
data.konto2 = data.konto2.replaceAll(search, replace);
row.update(data);
}
if (data.datum && data.datum.includes(search)) {
data.datum = data.datum.replaceAll(search, replace);
row.update(data);
}
if (data.betrag && data.betrag.includes(search)) {
data.betrag = data.betrag.replaceAll(search, replace);
row.update(data);
}
});
}
/***************************************************************************/
function sendToDB(inst) {
if (!inst) inst = activeInstance;
fetch(`/api/context?mandant=${inst.baseId}&subdir=${inst.context.subdir}&kpattern=${inst.context.ktofile}`)
.then(r => r.json())
.then(data => {
inst.context.data = data;
renderActiveContent(inst);
});
}
/***************************************************************************/
function addMonths(dateStr){
if(!dateStr || dateStr.length !== 8) return dateStr;
const y = parseInt(dateStr.substring(0,4));
const m = parseInt(dateStr.substring(4,6)) - 1;
const d = parseInt(dateStr.substring(6,8));
const dt = new Date(y, m, d);
if(isNaN(dt)) return dateStr;
dt.setMonth(dt.getMonth() + monthOffset);
const yy = dt.getFullYear();
const mm = String(dt.getMonth()+1).padStart(2,"0");
const dd = String(dt.getDate()).padStart(2,"0");
return `${yy}${mm}${dd}`;
}
/***************************************************************************/
function copySelection(table){
const rows = table.getSelectedData();
if(!rows || rows.length === 0) return;
window.copyBuffer = rows.map(r => ({...r}));
}
/***************************************************************************/
async function pasteSelection(table) {
if(!window.copyBuffer || window.copyBuffer.length === 0) return;
const selected = table.getSelectedRows();
let insertAfter = selected.length ? selected[selected.length-1] : null;
const insertedRows = [];
for(let rowData of window.copyBuffer){
let data = {...rowData};
if(monthOffset > 0 && data.datum){
data.datum = addMonths(data.datum);
}
let newRow;
if(insertAfter){
newRow = await table.addRow(data, false, insertAfter);
}else{
newRow = await table.addRow(data);
}
insertedRows.push(newRow);
insertAfter = newRow;
}
// Selection nach Render setzen
// setTimeout(()=>{
// const el = (insertedRows[0]).getElement();
// if(el){
// el.dispatchEvent(new MouseEvent("click",{bubbles:true}));
// }
// },4);
setTimeout(()=>{
table.deselectRow();
insertedRows.forEach(r=>{
if(r) r.select();
});
// Cursor auf erste neue Zeile
if(insertedRows.length){
insertedRows[0].getElement().scrollIntoView({block:"nearest"});
}
}, 30);
}
</script>
</body>
</html>