by M  ·  Persönlicher Finanzplan
oder
Name
Name
Betrag
'; }).join('')+''; } function eHTML(cid,e){ var amt=gA(e),cs=cid===null?'null':String(cid); var tag=e.tag||(e.fix?'fix':null); var tagLabel=tag?('#'+tag):'#tag'; var tagClass=tag?('active-'+tag):''; var rowClass=tag==='lifestyle'?' tag-lifestyle':tag==='sparen'?' tag-sparen':tag==='fix'?' fix-exp':''; return '
'+ ''+ '
'+ '
'+ '
'+ '
#fix
'+ '
#lifestyle
'+ '
#sparen
'+ '
kein Tag
'+ '
'+ '
'; ''+(e.name||'Bezeichnung')+''+nction eHTML(cid,e){ var amt=gA(e),cs=cid===null?'null':String(cid); var tag=e.tag||(e.fix?'fix':null); var tagLabel=tag?('#'+tag):'#tag'; var tagClass=tag?('active-'+tag):''; var rowClass=tag==='lifestyle'?' tag-lifestyle':tag==='sparen'?' tag-sparen':tag==='fix'?' fix-exp':''; return '
'+ ''+ '
'+ '
'+ '
'+ '
#fix
'+ '
#lifestyle
'+ '
#sparen
'+ '
kein Tag
'+ '
'+ '
'; } function rExp(){ let h=''; S.cats.forEach(c=>{ const tot=c.entries.reduce((s,e)=>s+gA(e),0); const quickRows=c.entries.map(e=>`
${e.name||'—'}
`).join(''); h+=`
${c.name||'Kategorie'} ${fmt(tot)}
Schnelleingabe — alle Monate
${quickRows}
${c.entries.map(e=>eHTML(c.id,e)).join('')}
`; }); if(S.unc.length)h+=`
${S.unc.map(e=>eHTML(null,e)).join('')}
`; document.getElementById('el').innerHTML=h; } function rGoals(){ const bal=tInc()-tExp(); document.getElementById('gav').textContent=fmt(Math.max(0,bal)); document.getElementById('gtr').textContent=fmt(S.goals.reduce((s,g)=>s+g.target,0)); document.getElementById('gcu').textContent=fmt(S.goals.reduce((s,g)=>s+g.current,0)); document.getElementById('gmo').textContent=fmt(S.goals.reduce((s,g)=>s+g.monthly,0)); const grid=document.getElementById('gg'); if(!S.goals.length){grid.innerHTML='';} else { grid.innerHTML=S.goals.map((g,i)=>{ const pct=g.target>0?Math.min(100,Math.round(g.current/g.target*100)):0; const col=CL[i%CL.length],linked=g.kw&&g.kw.trim(); const rd=reachDate(g),done=g.current>=g.target&&g.target>0; return `
Zielbetrag
Gespart
Rate/Mo
Keyword${linked?'↑ verknüpft':'aus Ausgaben'}
${fmt(g.current)} / ${fmt(g.target)}${pct}%
${done?`
✓ Ziel erreicht!
`:rd?`
Erreichung: ${fmtDate(rd)}
`:''}
`; }).join(''); } // Achievement Unlocked let ah='
Achievement Unlocked 🏆
'; if(!S.goals.length){ah+='
Noch keine Sparziele definiert.
';} S.goals.forEach((g,i)=>{ const rd=reachDate(g),done=g.current>=g.target&&g.target>0; const pct=g.target>0?Math.min(100,Math.round(g.current/g.target*100)):0,col=CL[i%CL.length]; ah+=`
${g.name||'—'}
${fmt(g.current)} / ${fmt(g.target)} · ${pct}%
${done?'
✓ Erreicht!
':rd?`
${fmtDate(rd)}
${g.monthly?fmt(g.monthly)+'/Mo':'—'}
`:'
'}
`; }); const achEl=document.getElementById('achievement-content'); if(achEl)achEl.innerHTML=ah; } function rOpt(){ render503020(); renderPie(); const inc=tInc(),fix=tFix(),exp=tExp(),sav=tSav(),varExp=exp-fix; const fixPct=inc>0?Math.round(fix/inc*100):0,varPct=inc>0?Math.round(varExp/inc*100):0,savPct=inc>0?Math.round(sav/inc*100):0; let h=`
Fixkosten
${fixPct}%
${fmt(fix)}
Variabel
${varPct}%
${fmt(varExp)}
Sparbetrag
${savPct}%
${fmt(sav)}
`; document.getElementById('opt-content').innerHTML=h; } function yearTotalForEntry(e){let t=0;for(let m=0;m<12;m++)t+=getAmtFor(e,`${Y}-${String(m+1).padStart(2,'0')}`);return t;} function yearIncomeTotal(){ var t=0; for(var m=0;m<12;m++){ t+=getInc(Y,m).reduce(function(s,e){return s+(e.amount||0);},0); } return t; } function rYear(){ const incY=yearIncomeTotal(),expY=allE().reduce((s,e)=>s+yearTotalForEntry(e),0),bal=incY-expY; let h=`
Sparbetrag
`; S.goals.forEach((g,i)=>{const pct=g.target>0?Math.min(100,Math.round(g.current/g.target*100)):0,col=CL[i%CL.length];const rd=reachDate(g),done=g.current>=g.target&&g.target>0;h+=``;}); h+=`
${g.name||'—'}${fmt(g.current)}/ ${fmt(g.target)}${done?'':rd?`${fmtDate(rd)}`:'—'}
Gesamt${fmt(S.goals.reduce((s,g)=>s+g.current,0))}
`; h+=`
Ausgaben ${Y}
`; S.cats.forEach(c=>{const cy=c.entries.reduce((s,e)=>s+yearTotalForEntry(e),0);h+=``;c.entries.forEach(e=>{const yr=yearTotalForEntry(e),mo=yr/12;h+=``;});}); h+=`
${c.name}${fmt(cy)}
${e.name||'—'}${e.fix?'#fix':''}${yr?fmt(yr):''}${mo?'('+fmt(mo)+'/Mo)':''}
Gesamt${fmt(expY)}
`; h+=`
Bilanz ${Y}
Einnahmen${fmt(incY)}
Ausgaben${fmt(expY)}
Differenz${fmt(bal)}
`; document.getElementById('jc').innerHTML=h; } // PROFIL function updateTopbarName(name){ const el=document.getElementById('topbar-name'); const sn=document.getElementById('sidebar-name'); if(el)el.textContent=name?name+'\u2019s ':''; if(sn)sn.textContent=name?name+'\u2019s Sfinanzph\u00e4re':''; } window.openProfil=()=>{ document.getElementById('profil-screen').style.display='flex'; if(window._userName)document.getElementById('profil-name').value=window._userName; if(window.currentUser)document.getElementById('profil-email').value=window.currentUser.email||''; delReset(); document.getElementById('name-feedback').textContent=''; document.getElementById('email-feedback').textContent=''; }; window.closeProfil=()=>{document.getElementById('profil-screen').style.display='none';}; document.getElementById('profil-screen').addEventListener('click',e=>{if(e.target===e.currentTarget)window.closeProfil();}); window.delStep1=()=>{document.getElementById('del-s1').style.display='none';document.getElementById('del-s2').style.display='block';document.getElementById('del-s3').style.display='none';}; window.delStep2=()=>{document.getElementById('del-s1').style.display='none';document.getElementById('del-s2').style.display='none';document.getElementById('del-s3').style.display='block';document.getElementById('delete-feedback').textContent='';}; window.delReset=()=>{document.getElementById('del-s1').style.display='block';document.getElementById('del-s2').style.display='none';document.getElementById('del-s3').style.display='none';}; window.exportCSV=()=>{ const rows=[['Typ','Kategorie','Name','Monat','Betrag']]; S.incomeFixed.forEach(e=>rows.push(['Einnahme (fix)','—',e.name||'','alle Monate',e.amount||0])); Object.entries(S.monthIncome).forEach(([month,entries])=>(entries||[]).forEach(e=>rows.push(['Einnahme','—',e.name||'',month,e.amount||0]))); S.cats.forEach(cat=>{cat.entries.forEach(e=>{const amt=(e.amounts.fixed!==undefined&&e.amounts.fixed!==null)?e.amounts.fixed:0;rows.push(['Ausgabe',cat.name,e.name||'',e.fix?'alle Monate':'variabel',amt]);Object.entries(e.amounts).forEach(([k,v])=>{if(k!=='fixed')rows.push(['Ausgabe (Monat)',cat.name,e.name||'',k,v]);});});}); S.unc.forEach(e=>rows.push(['Ausgabe','—',e.name||'','variabel',e.amounts.fixed||0])); S.goals.forEach(g=>{rows.push(['Sparziel','—',g.name||'','—',g.target||0]);rows.push(['Gespart','—',g.name||'','—',g.current||0]);}); const csv=rows.map(r=>r.map(c=>'"'+String(c).replace(/"/g,'""')+'"').join(',')).join('\n'); const blob=new Blob([csv],{type:'text/csv;charset=utf-8;'}); const url=URL.createObjectURL(blob); const a=document.createElement('a');a.href=url;a.download='phinancplan_export.csv';a.click();URL.revokeObjectURL(url); }; syncG();rAll(); // ── AMOUNT POPUP ── var _amtCb=null; window.openAmtPopup=function(type,cid,eid,label){ var cur=0; if(type==='exp'){ var e=cid===null?S.unc.find(function(x){return x.id===eid;}): (S.cats.find(function(c){return c.id===cid;})||{entries:[]}).entries.find(function(x){return x.id===eid;}); if(e)cur=gA(e); } else { var ei=S.incomeFixed.find(function(x){return x.id===eid;}); if(!ei){var k=mk();ei=(S.monthIncome[k]||[]).find(function(x){return x.id===eid;});} if(ei)cur=ei.amount||0; } var inp=document.getElementById('amt-popup-inp'); var nm=document.getElementById('amt-popup-name'); var lbl=document.getElementById('amt-popup-label'); if(inp){inp.value=cur||'';} if(nm)nm.textContent=label||''; if(lbl)lbl.textContent=type==='inc'?'Einnahme':'Ausgabe'; _amtCb=function(val){ if(type==='exp')uEA(cid,eid,val); else uIA(eid,val); }; var bg=document.getElementById('amt-popup-bg'); if(bg)bg.classList.add('open'); setTimeout(function(){if(inp){inp.focus();inp.select();}},80); }; // ── TAG SYSTEM ── window.setEntryTag=function(cid,eid,tag){ var e; if(cid===null)e=S.unc.find(function(x){return x.id===eid;}); else{var c=S.cats.find(function(x){return x.id===cid;});if(c)e=c.entries.find(function(x){return x.id===eid;});} if(!e)return; if(tag==='none'){e.tag=null;e.fix=false;} else if(tag==='fix'){e.tag='fix';e.fix=true;} else{e.tag=tag;e.fix=false;} syncG();rBudget();rGoals();if(window.cloudSave)cloudSave(); }; // ── HELPERS FOR TAGS ── function tTagAmt(tag){return allE().filter(function(e){return e.tag===tag;}).reduce(function(s,e){return s+gA(e);},0);} function tSavAll(){return allE().filter(function(e){return isSavAny(e)||e.tag==='sparen';}).reduce(function(s,e){return s+gA(e);},0);} // ── PIE CHART ── function renderPie(){ var inc=tInc(),fix=tFix(),life=tTagAmt('lifestyle'),sav=tSavAll(); var other=Math.max(0,inc-fix-life-sav); var total=Math.max(inc,1); var t50=Math.round(total*0.50),t30=Math.round(total*0.30),t20=Math.round(total*0.20); function slicePath(pct,sa,color){ if(pct<=0.001)return''; var r=80,cx=100,cy=100,s2=sa*2*Math.PI,e2=(sa+pct)*2*Math.PI; var x1=cx+r*Math.cos(s2),y1=cy+r*Math.sin(s2),x2=cx+r*Math.cos(e2),y2=cy+r*Math.sin(e2); return ''; } var slices=[ {pct:fix/total,color:'#8b6fa0',label:'#fix',amt:fix,target:t50,tpct:50}, {pct:life/total,color:'#c4a882',label:'#lifestyle',amt:life,target:t30,tpct:30}, {pct:sav/total,color:'#6b9e82',label:'#sparen',amt:sav,target:t20,tpct:20}, {pct:other/total,color:'#ddd8ce',label:'Sonstige',amt:other,target:0,tpct:0} ]; var angle=0,paths=''; slices.forEach(function(s){paths+=slicePath(s.pct,angle,s.color);angle+=s.pct;}); var svg=''+paths+''; var legend=slices.filter(function(s){return s.amt>0;}).map(function(s){ var pct=Math.round(s.pct*100); var ok=s.tpct===0||(s.label==='#sparen'?s.amt>=s.target:s.amt<=s.target); var badge=s.tpct>0?(''+(ok?'✓':'↑')+' '+s.tpct+'% Ziel: '+fmt(s.target)+''):''; return '
'+s.label+badge+'
'+pct+'% — '+fmt(s.amt)+'
'; }).join(''); var el=document.getElementById('pie-chart'); if(el)el.innerHTML='
'+svg+'
'+legend+'
'; } // ── 50/30/20 ── function render503020(){ var inc=tInc(),fix=tFix(),life=tTagAmt('lifestyle'),sav=tSavAll(); var el=document.getElementById('rule-503020'); if(!el)return; if(inc<=0){el.innerHTML='
Trage dein Gehalt unter Einnahmen ein — dann siehst du deine Empfehlungen.
';return;} var t50=Math.round(inc*0.50),t30=Math.round(inc*0.30),t20=Math.round(inc*0.20); function card(pct,color,label,desc,amt,target){ var ok=label==='Sparen'?amt>=target:amt<=target; return '
'+ '
'+pct+'%'+label+'
'+ '
'+desc+'
'+ '
'+ '
Dein Ziel
'+fmt(target)+'
⬤ Das ist dein Ziel
'+ '
Aktuell
'+fmt(amt)+' '+(ok?'✓':'↑')+'
'+ '
'+Math.round(amt/inc*100)+'% deines Einkommens
'+ '
'; } el.innerHTML= ''+ card('50','#8b6fa0','Fixkosten','Miete, Energie, Versicherungen — alles was jeden Monat fix anfällt.',fix,t50)+ card('30','#c4a882','Lifestyle','Lebensmittel, Ausgehen, Hobbys — variable Ausgaben.',life,t30)+ card('20','#6b9e82','Sparen','Notgroschen, Urlaub, Zukunftsvorsorge.',sav,t20); } window.resetAll=function(){ showDlg('Beträge auf Null', 'Alle Beträge auf 0. Kategorien, Einträge und Sparziele bleiben.', [ {l:'Abbrechen', fn:function(){}}, {l:'Ja, nullen', cls:'delete-btn', fn:function(){ S.incomeFixed.forEach(function(e){ e.amount=0; }); S.monthIncome={}; S.cats.forEach(function(cat){ cat.entries.forEach(function(e){ e.amounts={}; }); }); S.unc.forEach(function(e){ e.amounts={}; }); S.goals.forEach(function(g){ g.current=0; g.monthly=0; }); syncG();rAll(); if(window.cloudSave)cloudSave(); var fb=document.getElementById('reset-feedback'); if(fb){fb.className='profil-feedback ok';fb.textContent='✓ Alle Beträge auf Null.';} toast('✓ Beträge auf Null'); }} ] ); }; window.importCSV = function(file) { if(!file) return; var fb = document.getElementById('import-feedback'); var reader = new FileReader(); reader.onload = function(e) { try { var text = e.target.result; // Remove BOM if present if(text.charCodeAt(0) === 0xFEFF) text = text.slice(1); var lines = text.split(/\r?\n/).filter(function(l){ return l.trim(); }); if(lines.length < 2) throw new Error('Datei leer oder ungültig'); // Reset state S.incomeFixed = []; S.monthIncome = {}; S.cats = []; S.unc = []; S.goals = []; nid = 1; var catMap = {}; // name -> cat object lines.slice(1).forEach(function(line) { var cols = []; var cur = '', inQ = false; for(var i = 0; i < line.length; i++) { var c = line[i]; if(c === '"') { inQ = !inQ; } else if(c === ',' && !inQ) { cols.push(cur); cur = ''; } else cur += c; } cols.push(cur); var typ = (cols[0]||'').trim(); var kat = (cols[1]||'').trim(); var name = (cols[2]||'').trim(); var monat = (cols[3]||'').trim(); var betrag = parseFloat((cols[4]||'').trim()) || 0; if(typ === 'Einnahme (fix)') { S.incomeFixed.push({id:nid++, name:name, amount:betrag, fix:true}); } else if(typ === 'Einnahme') { if(!S.monthIncome[monat]) S.monthIncome[monat] = []; S.monthIncome[monat].push({id:nid++, name:name, amount:betrag, fix:false}); } else if(typ === 'Ausgabe') { var cat = catMap[kat]; if(!cat) { cat = {id:nid++, name:kat, entries:[]}; catMap[kat] = cat; S.cats.push(cat); } var entry = {id:nid++, name:name, amounts:{}, fix: monat==='alle Monate', tag:null}; if(monat === 'alle Monate') entry.amounts.fixed = betrag; else entry.amounts[monat] = betrag; cat.entries.push(entry); } else if(typ === 'Sparziel') { S.goals.push({id:nid++, name:name, target:betrag, current:0, monthly:0, kw:''}); } else if(typ === 'Gespart') { var g = S.goals.find(function(x){ return x.name === name; }); if(g) g.current = betrag; } }); syncG(); rAll(); if(window.cloudSave) cloudSave(); if(fb){ fb.className='profil-feedback ok'; fb.textContent='✓ Import erfolgreich — ' + (lines.length-1) + ' Zeilen geladen.'; } toast('✓ Daten importiert'); } catch(err) { if(fb){ fb.className='profil-feedback err'; fb.textContent='Fehler: ' + err.message; } console.error('Import error:', err); } }; reader.readAsText(file, 'UTF-8'); }; var _renameCb=null; window.openRenamePopup=function(type,cid,eid,name,label){ var inp=document.getElementById('rename-popup-inp'); var lbl=document.getElementById('rename-popup-label'); if(lbl)lbl.textContent=label||'Name'; if(inp)inp.value=name||''; _renameCb=function(val){ val=val.trim();if(!val)return; if(type==='cat'){var c=S.cats.find(function(x){return x.id===cid;});if(c){c.name=val;rExp();if(window.cloudSave)cloudSave();}} else if(type==='exp'){uEN(cid,eid,val);} else if(type==='inc'){uIN(eid,val);} }; var bg=document.getElementById('rename-popup-bg'); if(bg)bg.classList.add('open'); setTimeout(function(){if(inp){inp.focus();inp.select();}},80); }; (function(x){ return x.name === name; }); if(g) g.current = betrag; } }); syncG(); rAll(); if(window.cloudSave) cloudSave(); if(fb){ fb.className='profil-feedback ok'; fb.textContent='✓ Import erfolgreich — ' + (lines.length-1) + ' Zeilen geladen.'; } toast('✓ Daten importiert'); } catch(err) { if(fb){ fb.className='profil-feedback err'; fb.textContent='Fehler: ' + err.message; } console.error('Import error:', err); } }; reader.readAsText(file, 'UTF-8'); };