| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> |
| <meta name="mobile-web-app-capable" content="yes"> |
| <meta name="apple-mobile-web-app-capable" content="yes"> |
| <meta name="theme-color" content="#2563eb"> |
| <link rel="manifest" href="/manifest.json"> |
| <title>Expense Tracker</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| |
| .transaction-list { |
| max-height: 300px; |
| overflow-y: auto; |
| } |
| .transaction-list::-webkit-scrollbar { |
| width: 5px; |
| } |
| .transaction-list::-webkit-scrollbar-track { |
| background: #f1f1f1; |
| } |
| .transaction-list::-webkit-scrollbar-thumb { |
| background: #888; |
| border-radius: 10px; |
| } |
| .slide-up { |
| animation: slideUp 0.3s ease-out; |
| } |
| @keyframes slideUp { |
| from { transform: translateY(100%); opacity: 0; } |
| to { transform: translateY(0); opacity: 1; } |
| } |
| </style> |
| <link rel="apple-touch-icon" href="/icons/icon-192x192.png"> |
| <link rel="icon" type="image/png" href="/icons/icon-192x192.png"> |
| <link rel="shortcut icon" href="/favicon.ico"> |
| </head> |
| <body class="bg-gray-100 font-sans"> |
| <div class="max-w-md mx-auto min-h-screen bg-white shadow-lg relative overflow-hidden"> |
| |
| <header class="bg-blue-600 text-white p-4 shadow-md"> |
| <div class="flex justify-between items-center"> |
| <h1 class="text-xl font-bold">Expense Tracker</h1> |
| <div class="flex space-x-3"> |
| <button id="sms-btn" class="p-2 rounded-full bg-blue-700 active:bg-blue-900 transition active:scale-95"> |
| <i class="fas fa-sms"></i> |
| </button> |
| <button id="add-btn" class="p-2 rounded-full bg-blue-700 active:bg-blue-900 transition active:scale-95"> |
| <i class="fas fa-plus"></i> |
| </button> |
| </div> |
| </div> |
| </header> |
|
|
| |
| <div class="p-4 bg-gradient-to-r from-blue-500 to-blue-600 text-white"> |
| <p class="text-sm opacity-80">Current Balance</p> |
| <h2 id="balance" class="text-3xl font-bold">₹0.00</h2> |
| <div class="flex justify-between mt-4 text-sm"> |
| <div> |
| <p class="opacity-80">Income</p> |
| <p id="income" class="font-medium text-green-300">₹0.00</p> |
| </div> |
| <div> |
| <p class="opacity-80">Expenses</p> |
| <p id="expense" class="font-medium text-red-300">₹0.00</p> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="p-4"> |
| <h3 class="font-semibold text-gray-700 mb-2">Recent Transactions</h3> |
| <div id="transactions" class="transaction-list space-y-2"> |
| <div class="text-center py-10 text-gray-500"> |
| <i class="fas fa-exchange-alt text-4xl mb-2 opacity-30"></i> |
| <p>No transactions yet</p> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="add-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center p-4"> |
| <div class="bg-white rounded-lg w-full max-w-sm slide-up"> |
| <div class="p-4 border-b"> |
| <h3 class="font-semibold text-lg">Add Transaction</h3> |
| </div> |
| <div class="p-4"> |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-2">Type</label> |
| <div class="flex space-x-2"> |
| <button id="expense-type" class="flex-1 py-2 bg-red-500 text-white rounded-lg font-medium">Expense</button> |
| <button id="income-type" class="flex-1 py-2 bg-green-500 text-white rounded-lg font-medium">Income</button> |
| </div> |
| </div> |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-2">Amount</label> |
| <input type="number" id="amount" class="w-full p-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="0.00"> |
| </div> |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-2">Category</label> |
| <select id="category" class="w-full p-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"> |
| <option value="food">Food</option> |
| <option value="shopping">Shopping</option> |
| <option value="transport">Transport</option> |
| <option value="bills">Bills</option> |
| <option value="entertainment">Entertainment</option> |
| <option value="salary">Salary</option> |
| <option value="other">Other</option> |
| </select> |
| </div> |
| <div class="mb-4"> |
| <label class="block text-gray-700 mb-2">Description (Optional)</label> |
| <input type="text" id="description" class="w-full p-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="e.g. Dinner at restaurant"> |
| </div> |
| <div class="flex space-x-2"> |
| <button id="cancel-btn" class="flex-1 py-2 bg-gray-300 text-gray-800 rounded-lg font-medium">Cancel</button> |
| <button id="save-btn" class="flex-1 py-2 bg-blue-600 text-white rounded-lg font-medium">Save</button> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="sms-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center p-4"> |
| <div class="bg-white rounded-lg w-full max-w-sm slide-up"> |
| <div class="p-4 border-b"> |
| <h3 class="font-semibold text-lg">Auto-detect from SMS</h3> |
| </div> |
| <div class="p-4"> |
| <div class="mb-4"> |
| <p class="text-sm text-gray-600 mb-3">Recent SMS messages that may contain transaction details:</p> |
| <div id="sms-list" class="space-y-3 max-h-60 overflow-y-auto"> |
| |
| </div> |
| </div> |
| <div class="flex space-x-2"> |
| <button id="sms-cancel-btn" class="flex-1 py-2 bg-gray-300 text-gray-800 rounded-lg font-medium">Cancel</button> |
| <button id="refresh-sms-btn" class="flex-1 py-2 bg-blue-600 text-white rounded-lg font-medium">Refresh</button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| const addBtn = document.getElementById('add-btn'); |
| const smsBtn = document.getElementById('sms-btn'); |
| const addModal = document.getElementById('add-modal'); |
| const smsModal = document.getElementById('sms-modal'); |
| const cancelBtn = document.getElementById('cancel-btn'); |
| const smsCancelBtn = document.getElementById('sms-cancel-btn'); |
| const saveBtn = document.getElementById('save-btn'); |
| const refreshSmsBtn = document.getElementById('refresh-sms-btn'); |
| const expenseTypeBtn = document.getElementById('expense-type'); |
| const incomeTypeBtn = document.getElementById('income-type'); |
| const amountInput = document.getElementById('amount'); |
| const categorySelect = document.getElementById('category'); |
| const descriptionInput = document.getElementById('description'); |
| const transactionsContainer = document.getElementById('transactions'); |
| const balanceDisplay = document.getElementById('balance'); |
| const incomeDisplay = document.getElementById('income'); |
| const expenseDisplay = document.getElementById('expense'); |
| const smsList = document.getElementById('sms-list'); |
| |
| |
| let transactions = JSON.parse(localStorage.getItem('transactions')) || []; |
| let currentType = 'expense'; |
| |
| |
| updateBalance(); |
| renderTransactions(); |
| |
| |
| addBtn.addEventListener('click', () => addModal.classList.remove('hidden')); |
| smsBtn.addEventListener('click', showSmsModal); |
| cancelBtn.addEventListener('click', () => addModal.classList.add('hidden')); |
| smsCancelBtn.addEventListener('click', () => smsModal.classList.add('hidden')); |
| saveBtn.addEventListener('click', saveTransaction); |
| refreshSmsBtn.addEventListener('click', loadSmsMessages); |
| expenseTypeBtn.addEventListener('click', () => setType('expense')); |
| incomeTypeBtn.addEventListener('click', () => setType('income')); |
| |
| |
| function setType(type) { |
| currentType = type; |
| if (type === 'expense') { |
| expenseTypeBtn.classList.add('bg-red-600'); |
| expenseTypeBtn.classList.remove('bg-red-500'); |
| incomeTypeBtn.classList.add('bg-green-500'); |
| incomeTypeBtn.classList.remove('bg-green-600'); |
| } else { |
| incomeTypeBtn.classList.add('bg-green-600'); |
| incomeTypeBtn.classList.remove('bg-green-500'); |
| expenseTypeBtn.classList.add('bg-red-500'); |
| expenseTypeBtn.classList.remove('bg-red-600'); |
| } |
| } |
| |
| function saveTransaction() { |
| const amount = parseFloat(amountInput.value); |
| const category = categorySelect.value; |
| const description = descriptionInput.value.trim(); |
| |
| if (isNaN(amount) || amount <= 0) { |
| alert('Please enter a valid amount'); |
| return; |
| } |
| |
| const transaction = { |
| id: Date.now(), |
| type: currentType, |
| amount: amount, |
| category: category, |
| description: description || category, |
| date: new Date().toISOString() |
| }; |
| |
| transactions.push(transaction); |
| saveToLocalStorage(); |
| updateBalance(); |
| renderTransactions(); |
| resetForm(); |
| addModal.classList.add('hidden'); |
| } |
| |
| function resetForm() { |
| amountInput.value = ''; |
| categorySelect.value = 'food'; |
| descriptionInput.value = ''; |
| setType('expense'); |
| } |
| |
| function saveToLocalStorage() { |
| localStorage.setItem('transactions', JSON.stringify(transactions)); |
| } |
| |
| function updateBalance() { |
| const amounts = transactions.map(transaction => |
| transaction.type === 'income' ? transaction.amount : -transaction.amount |
| ); |
| |
| const total = amounts.reduce((acc, item) => acc + item, 0).toFixed(2); |
| const income = transactions |
| .filter(t => t.type === 'income') |
| .reduce((acc, t) => acc + t.amount, 0) |
| .toFixed(2); |
| const expense = transactions |
| .filter(t => t.type === 'expense') |
| .reduce((acc, t) => acc + t.amount, 0) |
| .toFixed(2); |
| |
| balanceDisplay.textContent = `₹${total}`; |
| incomeDisplay.textContent = `₹${income}`; |
| expenseDisplay.textContent = `₹${expense}`; |
| } |
| |
| function renderTransactions() { |
| if (transactions.length === 0) { |
| transactionsContainer.innerHTML = ` |
| <div class="text-center py-10 text-gray-500"> |
| <i class="fas fa-exchange-alt text-4xl mb-2 opacity-30"></i> |
| <p>No transactions yet</p> |
| </div> |
| `; |
| return; |
| } |
| |
| transactionsContainer.innerHTML = transactions |
| .sort((a, b) => new Date(b.date) - new Date(a.date)) |
| .map(transaction => ` |
| <div class="flex items-center justify-between p-3 bg-white rounded-lg shadow-sm border"> |
| <div class="flex items-center space-x-3"> |
| <div class="p-2 rounded-full ${transaction.type === 'income' ? 'bg-green-100 text-green-600' : 'bg-red-100 text-red-600'}"> |
| <i class="${getCategoryIcon(transaction.category)}"></i> |
| </div> |
| <div> |
| <p class="font-medium">${transaction.description}</p> |
| <p class="text-xs text-gray-500">${new Date(transaction.date).toLocaleDateString()}</p> |
| </div> |
| </div> |
| <p class="${transaction.type === 'income' ? 'text-green-600' : 'text-red-600'} font-medium"> |
| ${transaction.type === 'income' ? '+' : '-'}₹${transaction.amount.toFixed(2)} |
| </p> |
| </div> |
| `) |
| .join(''); |
| } |
| |
| function getCategoryIcon(category) { |
| const icons = { |
| food: 'fas fa-utensils', |
| shopping: 'fas fa-shopping-bag', |
| transport: 'fas fa-bus', |
| bills: 'fas fa-file-invoice-dollar', |
| entertainment: 'fas fa-film', |
| salary: 'fas fa-money-bill-wave', |
| other: 'fas fa-wallet' |
| }; |
| return icons[category] || icons.other; |
| } |
| |
| function showSmsModal() { |
| smsModal.classList.remove('hidden'); |
| loadSmsMessages(); |
| } |
| |
| function loadSmsMessages() { |
| |
| |
| const simulatedSms = [ |
| { |
| id: 1, |
| body: "You've spent ₹250.00 at Starbucks using your card ending with 1234.", |
| date: "2023-05-15T10:30:00" |
| }, |
| { |
| id: 2, |
| body: "Credit of ₹25,000.00 received from Company XYZ to your account.", |
| date: "2023-05-14T09:15:00" |
| }, |
| { |
| id: 3, |
| body: "Debit of ₹1,200.00 for electricity bill from your account.", |
| date: "2023-05-13T08:45:00" |
| }, |
| { |
| id: 4, |
| body: "Payment of ₹450.00 to Uber completed. Trip ID: UB12345", |
| date: "2023-05-12T19:30:00" |
| } |
| ]; |
| |
| smsList.innerHTML = simulatedSms |
| .map(sms => ` |
| <div class="p-3 border rounded-lg cursor-pointer hover:bg-gray-50" onclick="processSms(${sms.id})"> |
| <p class="text-sm mb-1">${sms.body}</p> |
| <p class="text-xs text-gray-500">${new Date(sms.date).toLocaleString()}</p> |
| </div> |
| `) |
| .join(''); |
| } |
| |
| |
| function processSms(smsId) { |
| |
| const simulatedSms = [ |
| { |
| id: 1, |
| body: "You've spent ₹250.00 at Starbucks using your card ending with 1234.", |
| date: "2023-05-15T10:30:00" |
| }, |
| { |
| id: 2, |
| body: "Credit of ₹25,000.00 received from Company XYZ to your account.", |
| date: "2023-05-14T09:15:00" |
| }, |
| { |
| id: 3, |
| body: "Debit of ₹1,200.00 for electricity bill from your account.", |
| date: "2023-05-13T08:45:00" |
| }, |
| { |
| id: 4, |
| body: "Payment of ₹450.00 to Uber completed. Trip ID: UB12345", |
| date: "2023-05-12T19:30:00" |
| } |
| ].find(sms => sms.id === smsId); |
| |
| if (!simulatedSms) return; |
| |
| |
| let amountMatch = simulatedSms.body.match(/₹([\d,]+\.\d{2})/); |
| if (!amountMatch) return; |
| |
| const amount = parseFloat(amountMatch[1].replace(/,/g, '')); |
| const isCredit = simulatedSms.body.toLowerCase().includes('credit'); |
| const isDebit = simulatedSms.body.toLowerCase().includes('debit') || |
| simulatedSms.body.toLowerCase().includes('spent') || |
| simulatedSms.body.toLowerCase().includes('payment'); |
| |
| let category = 'other'; |
| if (simulatedSms.body.includes('Starbucks')) category = 'food'; |
| else if (simulatedSms.body.includes('electricity')) category = 'bills'; |
| else if (simulatedSms.body.includes('Uber')) category = 'transport'; |
| else if (simulatedSms.body.includes('Company')) category = 'salary'; |
| |
| const transaction = { |
| id: Date.now(), |
| type: isCredit ? 'income' : 'expense', |
| amount: amount, |
| category: category, |
| description: simulatedSms.body.split('.')[0], |
| date: new Date().toISOString() |
| }; |
| |
| transactions.push(transaction); |
| saveToLocalStorage(); |
| updateBalance(); |
| renderTransactions(); |
| smsModal.classList.add('hidden'); |
| } |
| |
| |
| window.processSms = processSms; |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=dropit042/up5tairs" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |