up5tairs / index.html
dropit042's picture
undefined - Initial Deployment
570f9bf verified
<!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>
/* Custom styles */
.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 -->
<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>
<!-- Balance Display -->
<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>
<!-- Transaction List -->
<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>
<!-- Add Transaction Modal -->
<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>
<!-- SMS Modal -->
<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">
<!-- SMS items will be added here -->
</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>
// DOM Elements
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');
// State
let transactions = JSON.parse(localStorage.getItem('transactions')) || [];
let currentType = 'expense'; // Default to expense
// Initialize
updateBalance();
renderTransactions();
// Event Listeners
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'));
// Functions
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() {
// In a real app, this would fetch SMS from the device
// For demo purposes, we'll simulate some SMS messages
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('');
}
// This function would be called when an SMS is clicked
function processSms(smsId) {
// Find the simulated SMS
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;
// Parse the SMS (in a real app, this would be more sophisticated)
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() // Using current date instead of SMS date
};
transactions.push(transaction);
saveToLocalStorage();
updateBalance();
renderTransactions();
smsModal.classList.add('hidden');
}
// Make processSms available globally for SMS click events
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>