1
0
Fork 0
This repository has been archived on 2025-05-26. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
Checkpoint-Golang/public/js/cc.js
2025-05-26 12:42:36 -05:00

87 lines
No EOL
13 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.

document.addEventListener("DOMContentLoaded",()=>{const m=document.getElementById("cards-container"),f=document.getElementById("empty-state"),O=document.getElementById("search-input"),U=document.getElementById("add-card-btn"),$=document.getElementById("add-first-card-btn"),l=document.getElementById("card-modal"),H=document.getElementById("close-modal"),v=document.getElementById("card-form"),b=document.getElementById("modal-title"),u=document.getElementById("card-id"),P=document.getElementById("add-category-btn"),g=document.getElementById("categories-container"),t=document.getElementById("payment-modal"),R=document.getElementById("close-payment-modal"),C=document.getElementById("payment-form"),p=document.getElementById("payment-card-id"),w=document.getElementById("payment-card-name"),c=document.getElementById("payment-category"),h=document.getElementById("payment-amount"),d=document.getElementById("payment-date"),n=document.createElement("div");n.className="modal",n.id="payment-history-modal";let e=M();o(),d.valueAsDate=new Date,U.addEventListener("click",()=>x()),$.addEventListener("click",()=>x()),H.addEventListener("click",()=>s(l)),R.addEventListener("click",()=>s(t)),v.addEventListener("submit",I),P.addEventListener("click",()=>y("","","")),C.addEventListener("submit",B),O.addEventListener("input",W),m.addEventListener("click",L),K(),E();function E(){n.innerHTML=`
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">Payment History</h2>
<button class="close-modal" id="close-history-modal">&times;</button>
</div>
<div id="payment-history-container">
<!-- Payment history will be loaded here -->
</div>
</div>
`,document.body.appendChild(n),document.getElementById("close-history-modal").addEventListener("click",()=>{s(n)})}function L(t){const n=t.target,i=n.closest(".credit-card");if(!i)return;const a=i.dataset.id,s=e.find(e=>e.id===a);if(!s)return;if(n.closest(".payment-btn")){t.stopPropagation(),T(s);return}if(n.closest(".edit-btn")){t.stopPropagation(),F(s);return}if(n.closest(".delete-btn")){t.stopPropagation(),V(a);return}const o=n.closest(".category-item");if(o&&o.dataset.categoryName){const t=o.dataset.categoryName,e=s.categories.find(e=>e.name===t);e&&z(s,e)}}function M(){const e=localStorage.getItem("creditCards");return e?JSON.parse(e):[]}function i(){localStorage.setItem("creditCards",JSON.stringify(e))}function o(){if(Array.from(m.children).forEach(e=>{e.classList.contains("empty-state")||e.remove()}),e.length===0){f.style.display="block";return}f.style.display="none";let n=e;const t=O.value.toLowerCase().trim();t&&(n=e.filter(e=>{const n=e.name.toLowerCase().includes(t),s=e.bank.toLowerCase().includes(t),o=e.categories.some(e=>e.name.toLowerCase().includes(t));return n||s||o})),n.forEach(e=>{const t=k(e);m.insertBefore(t,f)})}function k(e){const n=document.createElement("div");n.className="credit-card",n.dataset.id=e.id;const i=new Date,s=i.getDate(),o=parseInt(e.statementDate);let t;if(s===o)t=0;else if(s<o)t=o-s;else{const e=new Date(i.getFullYear(),i.getMonth()+1,0).getDate();t=e-s+o}return n.innerHTML=`
<div class="card-header">
<div class="card-details">
<h2 class="card-title">${e.name}</h2>
<div class="card-bank">${e.bank}${e.lastDigits?` •••• ${e.lastDigits}`:""}</div>
</div>
<div class="card-actions">
<button type="button" class="action-btn payment-btn" title="Record Payment" aria-label="Record Payment">💰</button>
<button type="button" class="action-btn edit-btn" title="Edit Card" aria-label="Edit Card">✏️</button>
<button type="button" class="action-btn delete-btn" title="Delete Card" aria-label="Delete Card">🗑️</button>
</div>
</div>
<div class="card-info">
<div class="info-row">
<span class="info-label">Statement Cycle:</span>
<span class="info-value">Day ${e.statementDate}</span>
</div>
<div class="info-row">
<span class="info-label">Cycle Resets:</span>
<span class="info-value">${t===0?"Today":`In ${t} day${t!==1?"s":""}`}</span>
</div>
${e.expiryDate?`
<div class="info-row">
<span class="info-label">Expires:</span>
<span class="info-value">${A(e.expiryDate)}</span>
</div>
`:""}
</div>
<div class="rewards-categories">
${e.categories.map(e=>{const n=e.payments,s=n.reduce((e,t)=>e+parseFloat(t.amount),0),t=e.limit>0,o=t?s/e.limit*100:0,c=o>=75&&o<100,l=o>=100,i=n.length>0,a=(s*e.rate/100).toFixed(2),d=t?(e.limit*e.rate/100).toFixed(2):0,r=t?` ($${a}/$${d})`:` ($${a})`;return`
<div class="category-item ${i?"has-payments":""}" data-category-name="${e.name}">
<div class="category-header">
<span class="category-tag">${e.name}: ${e.rate}%</span>
${t?`
<span class="info-value">$${s.toFixed(2)} / $${e.limit.toFixed(2)}${r}</span>
`:`<span class="info-value">$${s.toFixed(2)}${r}</span>`}
</div>
${t?`
<div class="progress-bar ${c?"near-limit":""} ${l?"at-limit":""}">
<div class="progress-fill" style="width: ${Math.min(o,100)}%"></div>
</div>
`:""}
${i?`<div class="view-payments-link">View ${n.length} payment${n.length!==1?"s":""}</div>`:""}
</div>
`}).join("")}
</div>
`,n}function A(e){const[t,n]=e.split("-");return`${n}/${t}`}function S(e){const t=new Date(e);return t.toLocaleDateString("en-US",{month:"2-digit",day:"2-digit",year:"numeric"})}function x(){v.reset(),u.value="",b.textContent="Add New Card",j(),a(l)}function F(e){v.reset(),u.value=e.id,document.getElementById("card-name").value=e.name,document.getElementById("card-bank").value=e.bank,document.getElementById("last-digits").value=e.lastDigits||"",document.getElementById("expiry-date").value=e.expiryDate||"",document.getElementById("statement-date").value=e.statementDate,b.textContent="Edit Card",j(),e.categories.length===0||e.categories.forEach(e=>{y(e.name,e.rate,e.limit)}),a(l)}function T(e){p.value=e.id,w.textContent=e.name,c.innerHTML="",e.categories.forEach(e=>{if(!e)return;const t=document.createElement("option");if(t.value=e.name,t.textContent=`${e.name} (${e.rate}%)`,e.limit>0){const n=e.payments,s=n.reduce((e,t)=>e+parseFloat(t.amount),0),o=Math.max(0,e.limit-s);t.textContent+=` - $${o.toFixed(2)} remaining`}c.appendChild(t)}),h.value="",d.valueAsDate=new Date,document.getElementById("payment-note").value="",t.querySelector(".modal-title").textContent="Record Payment",t.querySelector(".save-btn").textContent="Record Payment",a(t)}function z(e,t){if(!e||!t)return;const s=document.getElementById("payment-history-container"),i=n.querySelector(".modal-title");i.textContent=`${e.name} - ${t.name} Payments`,s.innerHTML="";const o=t.payments;if(o.length===0)s.innerHTML=`
<div class="empty-state">
<h3>No payment history</h3>
<p>No payments have been recorded for this category yet.</p>
</div>
`;else{const n=[...o].sort((e,t)=>new Date(t.date)-new Date(e.date)),i=`
<div class="payment-history-list">
<div class="payment-history-header">
<span>Date</span>
<span>Amount</span>
<span>Note</span>
<span>Actions</span>
</div>
${n.map(e=>`
<div class="payment-history-item" data-payment-id="${e.id}">
<span class="payment-date">${S(e.date)}</span>
<span class="payment-amount">$${parseFloat(e.amount).toFixed(2)}</span>
<span class="payment-note">${e.note||"-"}</span>
<div class="payment-actions">
<button type="button" class="action-btn edit-payment-btn" title="Edit Payment" aria-label="Edit Payment">✏️</button>
<button type="button" class="action-btn delete-payment-btn" title="Delete Payment" aria-label="Delete Payment">🗑️</button>
</div>
</div>
`).join("")}
</div>
`;s.innerHTML=i,s.querySelectorAll(".edit-payment-btn").forEach(n=>{n.addEventListener("click",n=>{const i=n.target.closest(".payment-history-item").dataset.paymentId,s=o.find(e=>e.id===i);s&&D(e.id,t.name,s)})}),s.querySelectorAll(".delete-payment-btn").forEach(n=>{n.addEventListener("click",n=>{const i=n.target.closest(".payment-history-item").dataset.paymentId,s=o.find(e=>e.id===i);s&&N(e.id,t.name,s.id)})})}a(n)}function D(o,i,r){p.value=o;const m=e.find(e=>e.id===o);if(!m)return;w.textContent=m.name,c.innerHTML="";const l=m.categories.find(e=>e.name===i);if(!l)return;const f=document.createElement("option");f.value=l.name,f.textContent=`${l.name} (${l.rate}%)`,c.appendChild(f),h.value=r.amount,d.value=r.date,document.getElementById("payment-note").value=r.note||"";const u=document.getElementById("payment-id")||document.createElement("input");u.type="hidden",u.id="payment-id",u.value=r.id,document.getElementById("payment-id")||C.appendChild(u),t.querySelector(".modal-title").textContent="Edit Payment",t.querySelector(".save-btn").textContent="Update Payment",s(n),setTimeout(()=>{a(t)},300)}function N(t,a,c){if(!confirm("Are you sure you want to delete this payment?"))return;const l=e.findIndex(e=>e.id===t);if(l===-1)return;const h=e[l].categories.findIndex(e=>e.name===a);if(h===-1)return;const d=e[l].categories[h].payments,u=d.findIndex(e=>e.id===c);if(u===-1)return;const m=d[u].amount;d.splice(u,1),i(),s(n),r(`Payment of $${m.toFixed(2)} has been deleted`),o()}function a(e){e.classList.add("active"),document.body.style.overflow="hidden",setTimeout(()=>{const t=e.querySelector('input:not([type="hidden"])');t&&t.focus()},100)}function s(e){e.classList.remove("active"),document.body.style.overflow=""}function j(){g.innerHTML=""}function y(e="",t="",n=""){const s=document.createElement("div");s.className="category-inputs",s.innerHTML=`
<input type="text" class="form-input category-name" placeholder="Category (e.g. Dining)" value="${e}">
<input type="number" class="form-input category-rate" placeholder="Rate %" step="0.1" min="0" value="${t}">
<input type="number" class="form-input category-limit" placeholder="Limit $" step="0.01" min="0" value="${n}">
<button type="button" class="action-btn delete-btn remove-category" title="Remove Category" aria-label="Remove Category">×</button>
`,s.querySelector(".remove-category").addEventListener("click",function(){s.remove()}),g.appendChild(s)}function I(t){t.preventDefault();const a=u.value||_(),n=!!u.value,m=document.getElementById("card-name").value,f=document.getElementById("card-bank").value,p=document.getElementById("last-digits").value,v=document.getElementById("expiry-date").value,b=document.getElementById("statement-date").value,h=[],j=g.querySelectorAll(".category-inputs");j.forEach(t=>{const s=t.querySelector(".category-name").value.trim(),o=t.querySelector(".category-rate").value.trim(),i=t.querySelector(".category-limit").value.trim();if(!s&&!o&&!i)return;let c=[];const r=n?e.find(e=>e.id===a):null;if(n&&r&&r.categories){const e=r.categories.find(e=>e.name===s);e&&Array.isArray(e.payments)&&(c=e.payments)}h.push({name:s,rate:o?parseFloat(o):0,limit:i?parseFloat(i):null,payments:c})});const c={id:a,name:m,bank:f,lastDigits:p,expiryDate:v,statementDate:b,categories:h,createdAt:(new Date).toISOString()},d=n?e.findIndex(e=>e.id===a):-1;d!==-1?(c.archivedPayments=e[d].archivedPayments,e[d]=c):e.push(c),i(),o(),r(n?"Card updated successfully":"Card added successfully"),s(l)}function B(n){n.preventDefault();const b=p.value,j=c.value,l=parseFloat(h.value),f=d.value,g=document.getElementById("payment-note").value,u=document.getElementById("payment-id")?.value,m=e.findIndex(e=>e.id===b);if(m===-1)return;const v=e[m].categories.findIndex(e=>e.name===j);if(v===-1)return;const a=e[m].categories[v].payments;if(u){const e=a.findIndex(e=>e.id===u);e!==-1&&(a[e]={id:u,amount:l,date:f,note:g,createdAt:a[e].createdAt,updatedAt:(new Date).toISOString()},r("Payment updated successfully"))}else{const e={id:_(),amount:l,date:f,note:g,createdAt:(new Date).toISOString()};a.push(e),r(`Payment of $${l.toFixed(2)} recorded`)}i(),o(),document.getElementById("payment-id")&&document.getElementById("payment-id").remove(),t.querySelector(".modal-title").textContent="Record Payment",s(t)}function V(t){if(!confirm("Are you sure you want to delete this card?"))return;const n=e.find(e=>e.id===t)?.name||"Card";e=e.filter(e=>e.id!==t),i(),o(),r(`${n} has been deleted`)}function r(e){const n=document.querySelector(".toast");n&&n.remove();const t=document.createElement("div");t.className="toast",t.textContent=e,document.body.appendChild(t),setTimeout(()=>t.classList.add("show"),10),setTimeout(()=>{t.classList.remove("show"),setTimeout(()=>t.remove(),300)},3e3)}function W(){o()}function _(){return Date.now().toString(36)+Math.random().toString(36).substr(2,5)}function K(){const t=new Date,n=localStorage.getItem("lastCycleCheck");if(!n||new Date(n).toDateString()!==t.toDateString()){const n=t.getDate();e.forEach(e=>{const s=parseInt(e.statementDate);n===s&&e.categories.forEach(n=>{if(n.payments.length>0){e.archivedPayments||(e.archivedPayments=[]);const s={date:t.toISOString(),categories:[{name:n.name,rate:n.rate,payments:[...n.payments]}]};e.archivedPayments.push(s),n.payments=[]}})}),i(),localStorage.setItem("lastCycleCheck",t.toISOString())}}})