--- payment_methods: - Debit Card - PayPal - Credit Card - Crypto - Apple Pay currencies: - $ - € - £ - USDT subscriptions: --- ```dataviewjs await (async () => { const DEF_CUR="$",GRACE=3; const MO=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; const td=()=>new Date().toISOString().split('T')[0]; const td0=()=>{const d=new Date();d.setHours(0,0,0,0);return d;}; const pd=s=>s?new Date(s+'T00:00:00'):null; const fD=s=>{const d=pd(s);if(!d)return'—';return`${d.getDate()} ${MO[d.getMonth()]} ${String(d.getFullYear()).slice(2)}`;}; const dL=s=>{const d=pd(s);if(!d)return 0;return Math.ceil((d-td0())/864e5);}; const gid=()=>'s'+Date.now().toString(36)+'_'+Math.random().toString(36).slice(2,6); function fP(p,c){const n=Number(p)||0;const f=n%1===0?n.toLocaleString():n.toLocaleString(undefined,{minimumFractionDigits:2,maximumFractionDigits:2});if(c&&c.length===1)return c+f;return f+' '+(c||'$');} function adv(s,cy){const d=pd(s);if(!d)return s;switch(cy){case'monthly':d.setMonth(d.getMonth()+1);break;case'yearly':d.setFullYear(d.getFullYear()+1);break;case'weekly':d.setDate(d.getDate()+7);break;case'quarterly':d.setMonth(d.getMonth()+3);break;case'biannual':d.setMonth(d.getMonth()+6);break;default:d.setMonth(d.getMonth()+1);}return d.toISOString().split('T')[0];} const fCy=c=>({monthly:'Monthly',yearly:'Yearly',weekly:'Weekly',quarterly:'Quarterly',biannual:'Bi-annual'}[c]||c||''); const MC={NassPay:{dot:'#ef4444',text:'#fca5a5',bg:'rgba(239,68,68,.07)'},PayPal:{dot:'#3b82f6',text:'#93c5fd',bg:'rgba(59,130,246,.07)'},TIB:{dot:'#22c55e',text:'#86efac',bg:'rgba(34,197,94,.07)'},Crypto:{dot:'#7f1d1d',text:'#fca5a5',bg:'rgba(127,29,29,.07)'}}; const gmc=m=>MC[m]||{dot:'#6b7280',text:'#d1d5db',bg:'rgba(107,114,128,.07)'}; const cf=app.workspace.getActiveFile(); if(!cf){dv.paragraph('No active file.');return;} function rd(){const c=app.metadataCache.getFileCache(cf);const f=c?.frontmatter||{};return{subs:JSON.parse(JSON.stringify(f.subscriptions||[])),methods:JSON.parse(JSON.stringify(f.payment_methods||['NassPay','FIB','TIB','Crypto'])),currencies:JSON.parse(JSON.stringify(f.currencies||['$','€','£','IQD']))};} async function sv(subs,methods,currencies){await app.fileManager.processFrontMatter(cf,fm=>{fm.subscriptions=subs;if(methods)fm.payment_methods=methods;if(currencies)fm.currencies=currencies;});} let exRate=null; try{const ef=app.vault.getAbstractFileByPath('Habits/Expenses Calculator.md');if(ef){const ec=app.metadataCache.getFileCache(ef);if(ec?.frontmatter?.exchange_rate_100usd)exRate=Number(ec.frontmatter.exchange_rate_100usd)||null;}}catch(e){} function toUSD(price,cur){if(!price)return 0;const n=Number(price)||0;if(cur==='IQD'&&exRate)return(n/exRate)*100;return n;} function openDaily(dateStr){if(!dateStr)return;const path='Habits/Habit Days/'+dateStr+'.md';const file=app.vault.getAbstractFileByPath(path);if(file)app.workspace.openLinkText(path,'',true);} function dailyExists(dateStr){if(!dateStr)return false;return !!app.vault.getAbstractFileByPath('Habits/Habit Days/'+dateStr+'.md');} let D=rd(),ns=false;const nw=td(); D.subs.forEach(s=>{ if(!s.id){s.id=gid();ns=true;} if(s.type==='recurring'&&s.status==='active'&&s.nextDate){let a=false;while(s.nextDate{if(e.target===bg)cl();});cd.addEventListener('click',e=>e.stopPropagation());fn(cd,cl);bg.appendChild(cd);document.body.appendChild(bg);} function selO(arr,sel){return arr.map(v=>``).join('');} function openAE(es){ const ie=!!es;const sub=ie?{...es}:{name:'',price:'',currency:DEF_CUR,method:'',type:'recurring',cycle:'monthly',started:td(),nextDate:'',status:'active'}; openM((cd,cl)=>{ function bld(){ const ir=sub.type==='recurring';const sugDate=(ir&&sub.started)?adv(sub.started,sub.cycle||'monthly'):''; cd.innerHTML=`
${ie?'Edit':'New'} Subscription
${ir?`
`:''}
${ir&&sugDate&&!sub.nextDate?`
↳ ${fD(sugDate)} — click to apply
`:''}
Cancel
Save
`; cd.querySelector('#sf-hint')?.addEventListener('click',()=>{cd.querySelector('#sf-nd').value=sugDate;cd.querySelector('#sf-hint')?.remove();}); cd.querySelector('#sf-ty').addEventListener('change',e=>{sub.type=e.target.value;sub.name=cd.querySelector('#sf-n').value;sub.price=cd.querySelector('#sf-p').value;sub.currency=cd.querySelector('#sf-cur').value;sub.method=cd.querySelector('#sf-m').value;sub.started=cd.querySelector('#sf-ds').value;sub.nextDate=cd.querySelector('#sf-nd').value;if(cd.querySelector('#sf-cy'))sub.cycle=cd.querySelector('#sf-cy').value;bld();}); cd.querySelector('#sf-cy')?.addEventListener('change',e=>{sub.cycle=e.target.value;sub.name=cd.querySelector('#sf-n').value;sub.price=cd.querySelector('#sf-p').value;sub.currency=cd.querySelector('#sf-cur').value;sub.method=cd.querySelector('#sf-m').value;sub.started=cd.querySelector('#sf-ds').value;sub.nextDate='';sub.type=cd.querySelector('#sf-ty').value;bld();}); cd.querySelector('#sf-ds')?.addEventListener('change',e=>{sub.started=e.target.value;sub.name=cd.querySelector('#sf-n').value;sub.price=cd.querySelector('#sf-p').value;sub.currency=cd.querySelector('#sf-cur').value;sub.method=cd.querySelector('#sf-m').value;sub.nextDate='';sub.type=cd.querySelector('#sf-ty').value;if(cd.querySelector('#sf-cy'))sub.cycle=cd.querySelector('#sf-cy').value;bld();}); cd.querySelector('#sf-x').addEventListener('click',cl); cd.querySelector('#sf-s').addEventListener('click',async()=>{ const nm=cd.querySelector('#sf-n').value.trim();if(!nm){cd.querySelector('#sf-n').style.borderColor='#ef4444';return;} const mt=cd.querySelector('#sf-m').value;if(!mt){cd.querySelector('#sf-m').style.borderColor='#ef4444';return;} const ty=cd.querySelector('#sf-ty').value;let nD=cd.querySelector('#sf-nd').value; if(ty==='recurring'&&!nD&&cd.querySelector('#sf-ds').value)nD=adv(cd.querySelector('#sf-ds').value,cd.querySelector('#sf-cy')?.value||'monthly'); const o={id:ie?sub.id:gid(),name:nm,price:parseFloat(cd.querySelector('#sf-p').value)||0,currency:cd.querySelector('#sf-cur').value||DEF_CUR,method:mt,type:ty,cycle:ty==='recurring'?(cd.querySelector('#sf-cy')?.value||'monthly'):'',started:cd.querySelector('#sf-ds').value||td(),nextDate:nD||'',status:'active'}; if(ie&&es.cancelled)o.cancelled=es.cancelled; const subs=rd().subs;if(ie){const i=subs.findIndex(s=>s.id===sub.id);if(i>=0)subs[i]={...subs[i],...o};}else subs.push(o); await sv(subs);D=rd();cl();setTimeout(renderAll,150); }); }bld(); }); } function openCancel(sub){openM((cd,cl)=>{ const hasTime=sub.nextDate&&sub.nextDate>=nw; cd.innerHTML=`
Cancel Renewal
Stop recurring payments for ${sub.name}?${hasTime?`
Access until ${fD(sub.nextDate)}. Moves to Non-Recurring.
`:''}
Keep
Cancel
`; cd.querySelector('#cc-n').addEventListener('click',cl); cd.querySelector('#cc-y').addEventListener('click',async()=>{const subs=rd().subs;const s=subs.find(x=>x.id===sub.id);if(s){if(hasTime){s.type='non-recurring';s.cancelled=true;s.cycle='';}else{s.status='dead';s.deathDate=td();s.deathReason='cancelled';}}await sv(subs);D=rd();cl();setTimeout(renderAll,150);}); });} function openArchive(sub){openM((cd,cl)=>{ cd.innerHTML=`
Archive
Archive ${sub.name}?
Keep
Archive
`; cd.querySelector('#aa-n').addEventListener('click',cl); cd.querySelector('#aa-y').addEventListener('click',async()=>{const subs=rd().subs;const s=subs.find(x=>x.id===sub.id);if(s){s.status='dead';s.deathDate=td();s.deathReason='cancelled';}await sv(subs);D=rd();cl();setTimeout(renderAll,150);}); });} function openStats(){ if(mo)return;mo=true; const bg=document.createElement('div');bg.className='VA-mbg';bg.style.cssText='padding:12px !important'; const cd=document.createElement('div');cd.className='VA-stats'; function cl(){bg.remove();mo=false;} bg.addEventListener('click',e=>{if(e.target===bg)cl();});cd.addEventListener('click',e=>e.stopPropagation()); const subs=D.subs;const active=subs.filter(s=>s.status==='active');const rec=active.filter(s=>s.type==='recurring');const non=active.filter(s=>s.type==='non-recurring');const dead=subs.filter(s=>s.status==='dead'); // Monthly cost: only monthly-cycle recurring subs let monthlyCost=0;rec.forEach(s=>{if(s.cycle==='monthly')monthlyCost+=toUSD(s.price,s.currency);}); // Yearly cost: all recurring normalized to yearly let yearlyCost=0;rec.forEach(s=>{let u=toUSD(s.price,s.currency);switch(s.cycle){case'monthly':u*=12;break;case'weekly':u*=52;break;case'quarterly':u*=4;break;case'biannual':u*=2;break;case'yearly':break;default:u*=12;}yearlyCost+=u;}); // Costliest: highest raw USD price among all active, regardless of cycle let topSub=null,topVal=0;active.forEach(s=>{const v=toUSD(s.price,s.currency);if(v>topVal){topVal=v;topSub=s;}}); const methodCounts={};active.forEach(s=>{methodCounts[s.method]=(methodCounts[s.method]||0)+1;}); const maxMC=Math.max(...Object.values(methodCounts),1); const methodBars=Object.entries(methodCounts).sort((a,b)=>b[1]-a[1]).map(([m,c])=>{const mc=gmc(m);const pct=Math.round((c/maxMC)*100);return`
${m}
${c}
`;}).join(''); const curCounts={};active.forEach(s=>{curCounts[s.currency]=(curCounts[s.currency]||0)+1;}); const curText=Object.entries(curCounts).map(([c,n])=>`${c}: ${n}`).join(' · '); cd.innerHTML=`
Dashboard
×
Monthly Recurring$${monthlyCost.toFixed(2)}
Yearly Total$${yearlyCost.toFixed(0)}
Active${active.length}${rec.length} rec · ${non.length} one-time
Archived${dead.length}${dead.filter(s=>s.deathReason==='cancelled').length}c · ${dead.filter(s=>s.deathReason==='expired').length}e
Costliest${topSub?topSub.name:'—'}${topSub?fP(topSub.price,topSub.currency)+' · '+fCy(topSub.cycle):''}
Methods
${methodBars||''}
${curText}
`; cd.querySelector('#stx').addEventListener('click',cl); bg.appendChild(cd);document.body.appendChild(bg); setTimeout(()=>{cd.querySelectorAll('.VA-stbar-fill').forEach(b=>{const w=b.style.width;b.style.width='0%';setTimeout(()=>{b.style.width=w;},20);});},20); } function openLE(title,key){openM((cd,cl)=>{ let items=[...D[key]]; function ren(){ cd.innerHTML=`
${title}
${items.map((m,i)=>`
${m}×
`).join('')}
Add
Done
`; cd.querySelectorAll('[data-i]').forEach(el=>{el.addEventListener('click',()=>{items.splice(+el.dataset.i,1);ren();});}); cd.querySelector('#le-a').addEventListener('click',()=>{const v=cd.querySelector('#le-n').value.trim();if(v&&!items.includes(v)){items.push(v);ren();}}); cd.querySelector('#le-n').addEventListener('keydown',e=>{if(e.key==='Enter')cd.querySelector('#le-a').click();}); cd.querySelector('#le-d').addEventListener('click',async()=>{const subs=rd().subs;await sv(subs,key==='methods'?items:D.methods,key==='currencies'?items:D.currencies);D=rd();cl();setTimeout(renderAll,150);}); }ren(); });} function showGrace(sub){if(sub.type!=='recurring'||sub.status!=='active'||!sub.lastAdv)return false;return Math.ceil((td0()-pd(sub.lastAdv))/864e5)<=GRACE;} // p = padding shorthand function bRow(sub,sec){ const isDead=sec==='dead';const m=gmc(sub.method);const dl=dL(sub.nextDate); const dlC=dl<=3?'VA-dl-c':dl<=7?'VA-dl-w':'VA-dl-ok'; let gr='';if(sec==='recurring'&&showGrace(sub))gr=`didn't renew?`; const cBadge=sub.cancelled?'cancelled':''; const usdEq=ST.showUSD&&sub.currency==='IQD'&&exRate?`≈$${((Number(sub.price)/exRate)*100).toFixed(2)}`:''; const usdNorm=toUSD(sub.price,sub.currency); const sL=sub.started&&dailyExists(sub.started)?`${fD(sub.started)}`:`${fD(sub.started)}`; const P='style="padding:10px 12px !important"'; if(isDead){const rc=sub.deathReason==='expired';return`${sub.name}${fP(sub.price,sub.currency)}${usdEq}${sub.method||'—'}${sL}${fD(sub.deathDate||sub.nextDate)}${rc?'Expired':'Cancelled'}Revive`;} const isRec=sec==='recurring'; return`${sub.name}${gr}${cBadge}${fP(sub.price,sub.currency)}${usdEq}${fCy(sub.cycle)||'One-time'}${sub.method||'—'}${sL}${fD(sub.nextDate)}${dl}dEdit${isRec?` Cancel`:''} Archive`; } function bSec(title,subs,sec,color){ const isDead=sec==='dead';const open=isDead?ST.deadOpen:true; const secC=sec==='recurring'?'VA-sec-rec':sec==='dead'?'VA-sec-ded':'VA-sec-non'; const THP='style="padding:9px 12px !important"'; const aH=`NamePriceCycleMethodStarted${sec==='recurring'?'Renews':'Ends'}Days`; const dH=`NamePriceMethodStartedEndedStatus`; const rows=subs.map(s=>bRow(s,sec)).join(''); const empty=subs.length===0?`—`:''; const cf=ST.filters[sec]||'all';const fLabel=cf==='all'?'Filter':cf; const pills=`All`+D.methods.map(m=>{const c=gmc(m);return`${m}`;}).join(''); const cs=ST.sorts[sec]||'date-asc'; const sorts=[{v:'date-asc',l:'Soonest'},{v:'date-desc',l:'Latest'},{v:'price-desc',l:'Priciest'},{v:'price-asc',l:'Cheapest'},{v:'name-asc',l:'A→Z'}].map(s=>`${s.l}`).join(''); const usdToggle=exRate?`≈USD`:''; return`
${title}${subs.length}${isDead?`${open?'▾ hide':'▸ show'}`:''}
${usdToggle}
${!open?'':`
${fLabel}
${pills}
Sort:${sorts}
${isDead?dH:aH}${rows||empty}
`}
`; } function renderAll(){ D=rd();const subs=D.subs; const corners='
'; // Decorative scan lines positioned between sections const scans='
'; root.innerHTML=`${corners}
${scans}
Subscriptions
Add
Stats
Methods
Currencies
${bSec('Recurring',subs.filter(s=>s.status==='active'&&s.type==='recurring'),'recurring','#22d3ee')} ${bSec('Non-Recurring',subs.filter(s=>s.status==='active'&&s.type==='non-recurring'),'non-recurring','#a78bfa')} ${bSec('Archived',subs.filter(s=>s.status==='dead'),'dead','#4a5568')}`; bindEv(); } function applyF(sec){const tb=root.querySelector(`[data-tb="${sec}"]`);if(!tb)return;const si=root.querySelector(`.VA-srch[data-sr="${sec}"]`);const q=si?si.value.toLowerCase():'';const fv=ST.filters[sec]||'all';tb.querySelectorAll('tr[data-id]').forEach(r=>{r.style.display=(!q||(r.dataset.nm||'').includes(q))&&(fv==='all'||r.dataset.mt===fv)?'':'none';});} function applyS(sec){const tb=root.querySelector(`[data-tb="${sec}"]`);if(!tb)return;const s=ST.sorts[sec]||'date-asc';const[k,dir]=s.split('-');const asc=dir==='asc';const rows=Array.from(tb.querySelectorAll('tr[data-id]'));rows.sort((a,b)=>{if(k==='price')return asc?(parseFloat(a.dataset.pr)||0)-(parseFloat(b.dataset.pr)||0):(parseFloat(b.dataset.pr)||0)-(parseFloat(a.dataset.pr)||0);if(k==='name')return asc?(a.dataset.nm||'').localeCompare(b.dataset.nm||''):(b.dataset.nm||'').localeCompare(a.dataset.nm||'');return asc?(a.dataset.dt||'').localeCompare(b.dataset.dt||''):(b.dataset.dt||'').localeCompare(a.dataset.dt||'');});rows.forEach(r=>tb.appendChild(r));} function bindEv(){ root.querySelector('#st-add')?.addEventListener('click',()=>openAE(null)); root.querySelector('#st-stats')?.addEventListener('click',()=>openStats()); root.querySelector('#st-met')?.addEventListener('click',()=>openLE('Payment Methods','methods')); root.querySelector('#st-cur')?.addEventListener('click',()=>openLE('Currencies','currencies')); root.querySelectorAll('[data-tog="dead"]').forEach(e=>{e.addEventListener('click',()=>{ST.deadOpen=!ST.deadOpen;renderAll();});}); root.querySelectorAll('[data-usdtog]').forEach(e=>{e.addEventListener('click',()=>{ST.showUSD=!ST.showUSD;renderAll();});}); root.querySelectorAll('[data-act]').forEach(el=>{ el.addEventListener('click',e=>{ e.stopPropagation();const act=el.dataset.act,id=el.dataset.id;const sub=D.subs.find(s=>s.id===id);if(!sub)return; if(act==='edit')openAE(sub);else if(act==='cancel'||act==='didnt-renew')openCancel(sub);else if(act==='archive')openArchive(sub); else if(act==='reactivate'){(async()=>{const subs=rd().subs;const s=subs.find(x=>x.id===id);if(s){s.status='active';s.type='recurring';delete s.deathDate;delete s.deathReason;delete s.cancelled;if(!s.cycle)s.cycle='monthly';if(s.nextDate&&s.nextDate{el.addEventListener('click',e=>{e.stopPropagation();openDaily(el.dataset.open);});}); root.querySelectorAll('.VA-srch').forEach(inp=>{inp.addEventListener('input',()=>applyF(inp.dataset.sr));}); root.querySelectorAll('.VA-fwrap').forEach(fw=>{ fw.querySelector('.VA-fb').addEventListener('click',e=>{e.stopPropagation();root.querySelectorAll('.VA-fwrap.open').forEach(f=>{if(f!==fw)f.classList.remove('open');});fw.classList.toggle('open');}); fw.querySelectorAll('.VA-fp').forEach(fp=>{fp.addEventListener('click',e=>{e.stopPropagation();const sec=fp.dataset.fs;const val=fp.dataset.fv;ST.filters[sec]=val;fw.querySelectorAll('.VA-fp').forEach(x=>x.classList.remove('act'));fp.classList.add('act');const btn=fw.querySelector('.VA-fb');btn.querySelector('span:first-child').textContent=val==='all'?'Filter':val;if(val!=='all')btn.classList.add('act');else btn.classList.remove('act');fw.classList.remove('open');applyF(sec);});}); }); document.addEventListener('click',()=>{root.querySelectorAll('.VA-fwrap.open').forEach(f=>f.classList.remove('open'));}); root.querySelectorAll('.VA-sp').forEach(s=>{s.addEventListener('click',()=>{const sec=s.dataset.ss;ST.sorts[sec]=s.dataset.sv;root.querySelectorAll(`.VA-sp[data-ss="${sec}"]`).forEach(x=>x.classList.remove('act'));s.classList.add('act');applyS(sec);});}); } renderAll(); })(); ```