1
0
Fork 0

Import existing project

This commit is contained in:
Caileb 2025-05-26 12:42:36 -05:00
parent 7887817595
commit 80b0cc4939
125 changed files with 16980 additions and 0 deletions

87
public/js/cc.js Normal file
View file

@ -0,0 +1,87 @@
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())}}})