trial-space / index.html
nameadarsh's picture
Add 2 files
bb321e7 verified
Raw
History Blame Contribute Delete
67.9 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#4f46e5">
<meta name="description" content="Offline Finance Manager">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>FinTrack - Offline Finance Manager</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="manifest" href="/manifest.json">
<style>
@media (display-mode: standalone) {
header {
padding-top: env(safe-area-inset-top);
}
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
.fade-in {
animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.progress-ring__circle {
transition: stroke-dashoffset 0.35s;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
.dark {
background-color: #1a1a2e;
color: #e2e8f0;
}
.dark .bg-white {
background-color: #16213e !important;
}
.dark .text-gray-800 {
color: #e2e8f0 !important;
}
.dark .border-gray-200 {
border-color: #2d3748 !important;
}
.dark .bg-indigo-600 {
background-color: #4338ca !important;
}
.dark .bg-indigo-100 {
background-color: #312e81 !important;
}
.dark .text-indigo-600 {
color: #818cf8 !important;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="max-w-md mx-auto bg-white shadow-sm min-h-screen flex flex-col">
<!-- Header -->
<header class="bg-indigo-600 text-white p-4 sticky top-0 z-10">
<div class="flex justify-between items-center">
<h1 class="text-xl font-bold">FinTrack</h1>
<div class="flex items-center space-x-3">
<button id="themeToggle" class="text-white">
<i class="fas fa-moon"></i>
</button>
<button id="exportBtn" class="text-white">
<i class="fas fa-file-export"></i>
</button>
</div>
</div>
<!-- Summary Cards -->
<div class="mt-4 grid grid-cols-2 gap-2">
<div class="bg-indigo-500 rounded-lg p-3">
<p class="text-xs text-indigo-100">Total Balance</p>
<p class="font-bold text-lg" id="totalBalance">$0.00</p>
</div>
<div class="bg-indigo-500 rounded-lg p-3">
<p class="text-xs text-indigo-100">This Month</p>
<p class="font-bold text-lg" id="monthlyExpense">$0.00</p>
</div>
</div>
</header>
<!-- Main Content -->
<main class="flex-1 overflow-y-auto no-scrollbar p-4">
<!-- Navigation Tabs -->
<div class="flex border-b border-gray-200 mb-4">
<button id="dashboardTab" class="flex-1 py-2 font-medium text-indigo-600 border-b-2 border-indigo-600">Dashboard</button>
<button id="transactionsTab" class="flex-1 py-2 font-medium text-gray-500">Transactions</button>
<button id="budgetsTab" class="flex-1 py-2 font-medium text-gray-500">Budgets</button>
<button id="goalsTab" class="flex-1 py-2 font-medium text-gray-500">Goals</button>
</div>
<!-- Dashboard View -->
<div id="dashboardView" class="space-y-4">
<div class="bg-white rounded-lg shadow p-4">
<h2 class="font-bold text-lg mb-2">Expense Breakdown</h2>
<div class="h-64">
<canvas id="expenseChart"></canvas>
</div>
</div>
<div class="bg-white rounded-lg shadow p-4">
<h2 class="font-bold text-lg mb-2">Recent Transactions</h2>
<div id="recentTransactions" class="space-y-2">
<!-- Transactions will be added here -->
</div>
<button id="addTransactionBtn" class="mt-3 w-full bg-indigo-600 text-white py-2 rounded-lg flex items-center justify-center space-x-2">
<i class="fas fa-plus"></i>
<span>Add Transaction</span>
</button>
</div>
<div class="bg-white rounded-lg shadow p-4">
<h2 class="font-bold text-lg mb-2">Savings Goals</h2>
<div id="goalsList" class="space-y-3">
<!-- Goals will be added here -->
</div>
<button id="addGoalBtn" class="mt-3 w-full bg-indigo-600 text-white py-2 rounded-lg flex items-center justify-center space-x-2">
<i class="fas fa-plus"></i>
<span>Add Goal</span>
</button>
</div>
</div>
<!-- Transactions View -->
<div id="transactionsView" class="hidden">
<div class="flex justify-between items-center mb-4">
<h2 class="font-bold text-lg">All Transactions</h2>
<div class="flex space-x-2">
<button id="filterBtn" class="bg-gray-100 px-3 py-1 rounded-lg text-sm">
<i class="fas fa-filter"></i>
</button>
<button id="sortBtn" class="bg-gray-100 px-3 py-1 rounded-lg text-sm">
<i class="fas fa-sort-amount-down"></i>
</button>
</div>
</div>
<div id="allTransactions" class="space-y-2">
<!-- All transactions will be added here -->
</div>
</div>
<!-- Budgets View -->
<div id="budgetsView" class="hidden">
<h2 class="font-bold text-lg mb-4">Category Budgets</h2>
<div id="budgetsList" class="space-y-3">
<!-- Budgets will be added here -->
</div>
<button id="addBudgetBtn" class="mt-4 w-full bg-indigo-600 text-white py-2 rounded-lg flex items-center justify-center space-x-2">
<i class="fas fa-plus"></i>
<span>Add Budget</span>
</button>
</div>
<!-- Goals View -->
<div id="goalsView" class="hidden">
<h2 class="font-bold text-lg mb-4">Savings Goals</h2>
<div id="fullGoalsList" class="space-y-3">
<!-- Full goals list will be added here -->
</div>
<button id="addGoalBtn2" class="mt-4 w-full bg-indigo-600 text-white py-2 rounded-lg flex items-center justify-center space-x-2">
<i class="fas fa-plus"></i>
<span>Add Goal</span>
</button>
</div>
</main>
<!-- Bottom Navigation -->
<nav class="bg-white border-t border-gray-200 p-2 sticky bottom-0">
<div class="flex justify-around">
<button class="p-2 text-indigo-600">
<i class="fas fa-home text-xl"></i>
</button>
<button class="p-2 text-gray-500">
<i class="fas fa-chart-pie text-xl"></i>
</button>
<button id="addTransactionBtnBottom" class="bg-indigo-600 text-white p-3 rounded-full -mt-6 shadow-lg">
<i class="fas fa-plus text-xl"></i>
</button>
<button class="p-2 text-gray-500">
<i class="fas fa-wallet text-xl"></i>
</button>
<button class="p-2 text-gray-500">
<i class="fas fa-cog text-xl"></i>
</button>
</div>
</nav>
</div>
<!-- Add Transaction Modal -->
<div id="transactionModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-20 hidden">
<div class="bg-white rounded-lg w-full max-w-md max-h-[90vh] overflow-y-auto fade-in">
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="font-bold text-lg">Add Transaction</h3>
<button id="closeTransactionModal" class="text-gray-500">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-4 space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Amount</label>
<input type="number" id="transactionAmount" class="w-full p-2 border border-gray-300 rounded-lg" placeholder="0.00">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Category</label>
<div class="relative">
<select id="transactionCategory" class="w-full p-2 border border-gray-300 rounded-lg appearance-none">
<option value="food">Food</option>
<option value="transport">Transport</option>
<option value="bills">Bills</option>
<option value="shopping">Shopping</option>
<option value="entertainment">Entertainment</option>
<option value="other">Other</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<i class="fas fa-chevron-down text-gray-400"></i>
</div>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Description</label>
<input type="text" id="transactionDescription" class="w-full p-2 border border-gray-300 rounded-lg" placeholder="Optional">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Date</label>
<input type="date" id="transactionDate" class="w-full p-2 border border-gray-300 rounded-lg">
</div>
<div class="flex space-x-2">
<button id="expenseBtn" class="flex-1 bg-red-500 text-white py-2 rounded-lg">
Expense
</button>
<button id="incomeBtn" class="flex-1 bg-green-500 text-white py-2 rounded-lg">
Income
</button>
</div>
</div>
</div>
</div>
<!-- Add Goal Modal -->
<div id="goalModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-20 hidden">
<div class="bg-white rounded-lg w-full max-w-md max-h-[90vh] overflow-y-auto fade-in">
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="font-bold text-lg">Add Savings Goal</h3>
<button id="closeGoalModal" class="text-gray-500">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-4 space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Goal Name</label>
<input type="text" id="goalName" class="w-full p-2 border border-gray-300 rounded-lg" placeholder="e.g. Vacation">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Target Amount</label>
<input type="number" id="goalTarget" class="w-full p-2 border border-gray-300 rounded-lg" placeholder="0.00">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Current Amount</label>
<input type="number" id="goalCurrent" class="w-full p-2 border border-gray-300 rounded-lg" placeholder="0.00">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Target Date</label>
<input type="date" id="goalDate" class="w-full p-2 border border-gray-300 rounded-lg">
</div>
<button id="saveGoalBtn" class="w-full bg-indigo-600 text-white py-2 rounded-lg">
Save Goal
</button>
</div>
</div>
</div>
<!-- Add Budget Modal -->
<div id="budgetModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-20 hidden">
<div class="bg-white rounded-lg w-full max-w-md max-h-[90vh] overflow-y-auto fade-in">
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="font-bold text-lg">Add Budget</h3>
<button id="closeBudgetModal" class="text-gray-500">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-4 space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Category</label>
<div class="relative">
<select id="budgetCategory" class="w-full p-2 border border-gray-300 rounded-lg appearance-none">
<option value="food">Food</option>
<option value="transport">Transport</option>
<option value="bills">Bills</option>
<option value="shopping">Shopping</option>
<option value="entertainment">Entertainment</option>
<option value="other">Other</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<i class="fas fa-chevron-down text-gray-400"></i>
</div>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Amount</label>
<input type="number" id="budgetAmount" class="w-full p-2 border border-gray-300 rounded-lg" placeholder="0.00">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Period</label>
<div class="relative">
<select id="budgetPeriod" class="w-full p-2 border border-gray-300 rounded-lg appearance-none">
<option value="weekly">Weekly</option>
<option value="monthly">Monthly</option>
<option value="yearly">Yearly</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<i class="fas fa-chevron-down text-gray-400"></i>
</div>
</div>
</div>
<button id="saveBudgetBtn" class="w-full bg-indigo-600 text-white py-2 rounded-lg">
Save Budget
</button>
</div>
</div>
</div>
<!-- Filter Modal -->
<div id="filterModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-20 hidden">
<div class="bg-white rounded-lg w-full max-w-md max-h-[90vh] overflow-y-auto fade-in">
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="font-bold text-lg">Filter Transactions</h3>
<button id="closeFilterModal" class="text-gray-500">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-4 space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Category</label>
<div class="relative">
<select id="filterCategory" class="w-full p-2 border border-gray-300 rounded-lg appearance-none">
<option value="all">All Categories</option>
<option value="food">Food</option>
<option value="transport">Transport</option>
<option value="bills">Bills</option>
<option value="shopping">Shopping</option>
<option value="entertainment">Entertainment</option>
<option value="other">Other</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<i class="fas fa-chevron-down text-gray-400"></i>
</div>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Type</label>
<div class="relative">
<select id="filterType" class="w-full p-2 border border-gray-300 rounded-lg appearance-none">
<option value="all">All Types</option>
<option value="expense">Expense</option>
<option value="income">Income</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<i class="fas fa-chevron-down text-gray-400"></i>
</div>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">From Date</label>
<input type="date" id="filterFromDate" class="w-full p-2 border border-gray-300 rounded-lg">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">To Date</label>
<input type="date" id="filterToDate" class="w-full p-2 border border-gray-300 rounded-lg">
</div>
</div>
<div class="flex space-x-2">
<button id="resetFilterBtn" class="flex-1 bg-gray-200 text-gray-800 py-2 rounded-lg">
Reset
</button>
<button id="applyFilterBtn" class="flex-1 bg-indigo-600 text-white py-2 rounded-lg">
Apply
</button>
</div>
</div>
</div>
</div>
<!-- Sort Modal -->
<div id="sortModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-20 hidden">
<div class="bg-white rounded-lg w-full max-w-md max-h-[90vh] overflow-y-auto fade-in">
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="font-bold text-lg">Sort Transactions</h3>
<button id="closeSortModal" class="text-gray-500">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-4 space-y-3">
<div class="flex items-center justify-between p-2 border-b border-gray-100">
<span>Date (Newest First)</span>
<input type="radio" name="sortOption" value="dateDesc" checked class="h-4 w-4 text-indigo-600">
</div>
<div class="flex items-center justify-between p-2 border-b border-gray-100">
<span>Date (Oldest First)</span>
<input type="radio" name="sortOption" value="dateAsc" class="h-4 w-4 text-indigo-600">
</div>
<div class="flex items-center justify-between p-2 border-b border-gray-100">
<span>Amount (Highest First)</span>
<input type="radio" name="sortOption" value="amountDesc" class="h-4 w-4 text-indigo-600">
</div>
<div class="flex items-center justify-between p-2 border-b border-gray-100">
<span>Amount (Lowest First)</span>
<input type="radio" name="sortOption" value="amountAsc" class="h-4 w-4 text-indigo-600">
</div>
<div class="flex items-center justify-between p-2">
<span>Category (A-Z)</span>
<input type="radio" name="sortOption" value="categoryAsc" class="h-4 w-4 text-indigo-600">
</div>
<button id="applySortBtn" class="w-full mt-4 bg-indigo-600 text-white py-2 rounded-lg">
Apply Sort
</button>
</div>
</div>
</div>
<!-- Export Modal -->
<div id="exportModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-20 hidden">
<div class="bg-white rounded-lg w-full max-w-md max-h-[90vh] overflow-y-auto fade-in">
<div class="p-4 border-b border-gray-200 flex justify-between items-center">
<h3 class="font-bold text-lg">Export Data</h3>
<button id="closeExportModal" class="text-gray-500">
<i class="fas fa-times"></i>
</button>
</div>
<div class="p-4 space-y-4">
<p class="text-sm text-gray-600">Export your financial data for backup or analysis.</p>
<div class="space-y-2">
<button id="exportJSONBtn" class="w-full bg-gray-100 text-gray-800 py-3 rounded-lg flex items-center justify-center space-x-2">
<i class="fas fa-file-code"></i>
<span>Export as JSON</span>
</button>
<button id="exportCSVBtn" class="w-full bg-gray-100 text-gray-800 py-3 rounded-lg flex items-center justify-center space-x-2">
<i class="fas fa-file-csv"></i>
<span>Export as CSV</span>
</button>
<button id="importBtn" class="w-full bg-gray-100 text-gray-800 py-3 rounded-lg flex items-center justify-center space-x-2">
<i class="fas fa-file-import"></i>
<span>Import Data</span>
</button>
</div>
<div class="hidden" id="importSection">
<label class="block text-sm font-medium text-gray-700 mb-1">Select file to import</label>
<input type="file" id="importFile" class="w-full p-2 border border-gray-300 rounded-lg">
<button id="confirmImportBtn" class="w-full mt-2 bg-indigo-600 text-white py-2 rounded-lg">
Confirm Import
</button>
</div>
</div>
</div>
</div>
<script>
// Initialize local storage if not exists
if (!localStorage.getItem('transactions')) {
localStorage.setItem('transactions', JSON.stringify([]));
}
if (!localStorage.getItem('goals')) {
localStorage.setItem('goals', JSON.stringify([]));
}
if (!localStorage.getItem('budgets')) {
localStorage.setItem('budgets', JSON.stringify([]));
}
if (!localStorage.getItem('settings')) {
localStorage.setItem('settings', JSON.stringify({
theme: 'light',
currency: '$'
}));
}
// DOM Elements
const dashboardView = document.getElementById('dashboardView');
const transactionsView = document.getElementById('transactionsView');
const budgetsView = document.getElementById('budgetsView');
const goalsView = document.getElementById('goalsView');
const dashboardTab = document.getElementById('dashboardTab');
const transactionsTab = document.getElementById('transactionsTab');
const budgetsTab = document.getElementById('budgetsTab');
const goalsTab = document.getElementById('goalsTab');
const transactionModal = document.getElementById('transactionModal');
const goalModal = document.getElementById('goalModal');
const budgetModal = document.getElementById('budgetModal');
const filterModal = document.getElementById('filterModal');
const sortModal = document.getElementById('sortModal');
const exportModal = document.getElementById('exportModal');
const addTransactionBtn = document.getElementById('addTransactionBtn');
const addTransactionBtnBottom = document.getElementById('addTransactionBtnBottom');
const addGoalBtn = document.getElementById('addGoalBtn');
const addGoalBtn2 = document.getElementById('addGoalBtn2');
const addBudgetBtn = document.getElementById('addBudgetBtn');
const filterBtn = document.getElementById('filterBtn');
const sortBtn = document.getElementById('sortBtn');
const exportBtn = document.getElementById('exportBtn');
const closeTransactionModal = document.getElementById('closeTransactionModal');
const closeGoalModal = document.getElementById('closeGoalModal');
const closeBudgetModal = document.getElementById('closeBudgetModal');
const closeFilterModal = document.getElementById('closeFilterModal');
const closeSortModal = document.getElementById('closeSortModal');
const closeExportModal = document.getElementById('closeExportModal');
const transactionAmount = document.getElementById('transactionAmount');
const transactionCategory = document.getElementById('transactionCategory');
const transactionDescription = document.getElementById('transactionDescription');
const transactionDate = document.getElementById('transactionDate');
const expenseBtn = document.getElementById('expenseBtn');
const incomeBtn = document.getElementById('incomeBtn');
const goalName = document.getElementById('goalName');
const goalTarget = document.getElementById('goalTarget');
const goalCurrent = document.getElementById('goalCurrent');
const goalDate = document.getElementById('goalDate');
const saveGoalBtn = document.getElementById('saveGoalBtn');
const budgetCategory = document.getElementById('budgetCategory');
const budgetAmount = document.getElementById('budgetAmount');
const budgetPeriod = document.getElementById('budgetPeriod');
const saveBudgetBtn = document.getElementById('saveBudgetBtn');
const filterCategory = document.getElementById('filterCategory');
const filterType = document.getElementById('filterType');
const filterFromDate = document.getElementById('filterFromDate');
const filterToDate = document.getElementById('filterToDate');
const resetFilterBtn = document.getElementById('resetFilterBtn');
const applyFilterBtn = document.getElementById('applyFilterBtn');
const applySortBtn = document.getElementById('applySortBtn');
const exportJSONBtn = document.getElementById('exportJSONBtn');
const exportCSVBtn = document.getElementById('exportCSVBtn');
const importBtn = document.getElementById('importBtn');
const importSection = document.getElementById('importSection');
const importFile = document.getElementById('importFile');
const confirmImportBtn = document.getElementById('confirmImportBtn');
const totalBalance = document.getElementById('totalBalance');
const monthlyExpense = document.getElementById('monthlyExpense');
const recentTransactions = document.getElementById('recentTransactions');
const allTransactions = document.getElementById('allTransactions');
const goalsList = document.getElementById('goalsList');
const fullGoalsList = document.getElementById('fullGoalsList');
const budgetsList = document.getElementById('budgetsList');
const themeToggle = document.getElementById('themeToggle');
// Chart
const expenseChartCtx = document.getElementById('expenseChart').getContext('2d');
let expenseChart;
// Current date for default values
const today = new Date();
const todayFormatted = today.toISOString().split('T')[0];
transactionDate.value = todayFormatted;
goalDate.valueAsDate = new Date(today.setMonth(today.getMonth() + 1));
// Event Listeners
dashboardTab.addEventListener('click', () => switchView('dashboard'));
transactionsTab.addEventListener('click', () => switchView('transactions'));
budgetsTab.addEventListener('click', () => switchView('budgets'));
goalsTab.addEventListener('click', () => switchView('goals'));
addTransactionBtn.addEventListener('click', () => transactionModal.classList.remove('hidden'));
addTransactionBtnBottom.addEventListener('click', () => transactionModal.classList.remove('hidden'));
addGoalBtn.addEventListener('click', () => goalModal.classList.remove('hidden'));
addGoalBtn2.addEventListener('click', () => goalModal.classList.remove('hidden'));
addBudgetBtn.addEventListener('click', () => budgetModal.classList.remove('hidden'));
filterBtn.addEventListener('click', () => filterModal.classList.remove('hidden'));
sortBtn.addEventListener('click', () => sortModal.classList.remove('hidden'));
exportBtn.addEventListener('click', () => exportModal.classList.remove('hidden'));
closeTransactionModal.addEventListener('click', () => transactionModal.classList.add('hidden'));
closeGoalModal.addEventListener('click', () => goalModal.classList.add('hidden'));
closeBudgetModal.addEventListener('click', () => budgetModal.classList.add('hidden'));
closeFilterModal.addEventListener('click', () => filterModal.classList.add('hidden'));
closeSortModal.addEventListener('click', () => sortModal.classList.add('hidden'));
closeExportModal.addEventListener('click', () => exportModal.classList.add('hidden'));
expenseBtn.addEventListener('click', () => saveTransaction('expense'));
incomeBtn.addEventListener('click', () => saveTransaction('income'));
saveGoalBtn.addEventListener('click', saveGoal);
saveBudgetBtn.addEventListener('click', saveBudget);
resetFilterBtn.addEventListener('click', resetFilters);
applyFilterBtn.addEventListener('click', applyFilters);
applySortBtn.addEventListener('click', applySort);
exportJSONBtn.addEventListener('click', exportJSON);
exportCSVBtn.addEventListener('click', exportCSV);
importBtn.addEventListener('click', () => importSection.classList.toggle('hidden'));
confirmImportBtn.addEventListener('click', importData);
themeToggle.addEventListener('click', toggleTheme);
// Initialize app
initApp();
// Functions
function initApp() {
// Load theme
const settings = JSON.parse(localStorage.getItem('settings'));
if (settings.theme === 'dark') {
document.body.classList.add('dark');
themeToggle.innerHTML = '<i class="fas fa-sun"></i>';
}
// Set currency
totalBalance.textContent = settings.currency + '0.00';
monthlyExpense.textContent = settings.currency + '0.00';
// Load data
updateDashboard();
loadTransactions();
loadGoals();
loadBudgets();
}
function switchView(view) {
dashboardView.classList.add('hidden');
transactionsView.classList.add('hidden');
budgetsView.classList.add('hidden');
goalsView.classList.add('hidden');
dashboardTab.classList.remove('text-indigo-600', 'border-indigo-600');
dashboardTab.classList.add('text-gray-500');
transactionsTab.classList.remove('text-indigo-600', 'border-indigo-600');
transactionsTab.classList.add('text-gray-500');
budgetsTab.classList.remove('text-indigo-600', 'border-indigo-600');
budgetsTab.classList.add('text-gray-500');
goalsTab.classList.remove('text-indigo-600', 'border-indigo-600');
goalsTab.classList.add('text-gray-500');
switch(view) {
case 'dashboard':
dashboardView.classList.remove('hidden');
dashboardTab.classList.remove('text-gray-500');
dashboardTab.classList.add('text-indigo-600', 'border-indigo-600');
break;
case 'transactions':
transactionsView.classList.remove('hidden');
transactionsTab.classList.remove('text-gray-500');
transactionsTab.classList.add('text-indigo-600', 'border-indigo-600');
break;
case 'budgets':
budgetsView.classList.remove('hidden');
budgetsTab.classList.remove('text-gray-500');
budgetsTab.classList.add('text-indigo-600', 'border-indigo-600');
break;
case 'goals':
goalsView.classList.remove('hidden');
goalsTab.classList.remove('text-gray-500');
goalsTab.classList.add('text-indigo-600', 'border-indigo-600');
break;
}
}
function saveTransaction(type) {
const amount = parseFloat(transactionAmount.value);
const category = transactionCategory.value;
const description = transactionDescription.value;
const date = transactionDate.value;
if (!amount || amount <= 0) {
alert('Please enter a valid amount');
return;
}
const transactions = JSON.parse(localStorage.getItem('transactions'));
const newTransaction = {
id: Date.now(),
amount: type === 'expense' ? -amount : amount,
category,
description,
date,
type
};
transactions.push(newTransaction);
localStorage.setItem('transactions', JSON.stringify(transactions));
// Reset form
transactionAmount.value = '';
transactionDescription.value = '';
transactionDate.value = todayFormatted;
// Close modal
transactionModal.classList.add('hidden');
// Update UI
updateDashboard();
loadTransactions();
}
function saveGoal() {
const name = goalName.value;
const target = parseFloat(goalTarget.value);
const current = parseFloat(goalCurrent.value);
const date = goalDate.value;
if (!name || !target || target <= 0 || current < 0) {
alert('Please fill all required fields with valid values');
return;
}
const goals = JSON.parse(localStorage.getItem('goals'));
const newGoal = {
id: Date.now(),
name,
target,
current,
date
};
goals.push(newGoal);
localStorage.setItem('goals', JSON.stringify(goals));
// Reset form
goalName.value = '';
goalTarget.value = '';
goalCurrent.value = '';
goalDate.valueAsDate = new Date(today.setMonth(today.getMonth() + 1));
// Close modal
goalModal.classList.add('hidden');
// Update UI
updateDashboard();
loadGoals();
}
function saveBudget() {
const category = budgetCategory.value;
const amount = parseFloat(budgetAmount.value);
const period = budgetPeriod.value;
if (!amount || amount <= 0) {
alert('Please enter a valid amount');
return;
}
const budgets = JSON.parse(localStorage.getItem('budgets'));
// Check if budget for this category already exists
const existingBudgetIndex = budgets.findIndex(b => b.category === category);
if (existingBudgetIndex !== -1) {
budgets[existingBudgetIndex] = {
category,
amount,
period
};
} else {
budgets.push({
category,
amount,
period
});
}
localStorage.setItem('budgets', JSON.stringify(budgets));
// Reset form
budgetAmount.value = '';
// Close modal
budgetModal.classList.add('hidden');
// Update UI
updateDashboard();
loadBudgets();
}
function updateDashboard() {
const transactions = JSON.parse(localStorage.getItem('transactions'));
const goals = JSON.parse(localStorage.getItem('goals'));
const budgets = JSON.parse(localStorage.getItem('budgets'));
const settings = JSON.parse(localStorage.getItem('settings'));
// Calculate total balance
const balance = transactions.reduce((sum, t) => sum + t.amount, 0);
totalBalance.textContent = settings.currency + balance.toFixed(2);
// Calculate monthly expenses
const currentMonth = new Date().getMonth();
const currentYear = new Date().getFullYear();
const monthlyExpenses = transactions
.filter(t => {
const date = new Date(t.date);
return date.getMonth() === currentMonth &&
date.getFullYear() === currentYear &&
t.type === 'expense';
})
.reduce((sum, t) => sum + Math.abs(t.amount), 0);
monthlyExpense.textContent = settings.currency + monthlyExpenses.toFixed(2);
// Update recent transactions (last 5)
recentTransactions.innerHTML = '';
const recent = [...transactions].sort((a, b) => new Date(b.date) - new Date(a.date)).slice(0, 5);
recent.forEach(t => {
const transactionEl = document.createElement('div');
transactionEl.className = 'flex justify-between items-center p-2 bg-gray-50 rounded-lg';
const iconMap = {
food: 'utensils',
transport: 'bus',
bills: 'file-invoice-dollar',
shopping: 'shopping-bag',
entertainment: 'film',
other: 'ellipsis-h'
};
transactionEl.innerHTML = `
<div class="flex items-center space-x-3">
<div class="p-2 rounded-full bg-${t.type === 'expense' ? 'red' : 'green'}-100 text-${t.type === 'expense' ? 'red' : 'green'}-600">
<i class="fas fa-${iconMap[t.category] || 'ellipsis-h'}"></i>
</div>
<div>
<p class="font-medium">${t.category.charAt(0).toUpperCase() + t.category.slice(1)}</p>
<p class="text-xs text-gray-500">${t.date} ${t.description ? '· ' + t.description : ''}</p>
</div>
</div>
<p class="font-medium text-${t.type === 'expense' ? 'red' : 'green'}-600">${t.type === 'expense' ? '-' : '+'}${settings.currency}${Math.abs(t.amount).toFixed(2)}</p>
`;
recentTransactions.appendChild(transactionEl);
});
// Update goals list
goalsList.innerHTML = '';
fullGoalsList.innerHTML = '';
goals.forEach(g => {
const progress = (g.current / g.target) * 100;
const daysLeft = Math.ceil((new Date(g.date) - new Date()) / (1000 * 60 * 60 * 24));
const goalEl = document.createElement('div');
goalEl.className = 'bg-white rounded-lg shadow p-4';
goalEl.innerHTML = `
<div class="flex justify-between items-start mb-2">
<h3 class="font-medium">${g.name}</h3>
<span class="text-sm ${daysLeft < 0 ? 'text-red-500' : 'text-gray-500'}">${daysLeft < 0 ? 'Expired' : daysLeft + ' days left'}</span>
</div>
<div class="flex justify-between text-sm mb-3">
<span>${settings.currency}${g.current.toFixed(2)}</span>
<span>${settings.currency}${g.target.toFixed(2)}</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div class="bg-indigo-600 h-2 rounded-full" style="width: ${Math.min(progress, 100)}%"></div>
</div>
<div class="flex justify-between mt-3">
<button class="text-xs text-indigo-600 edit-goal" data-id="${g.id}">
<i class="fas fa-edit mr-1"></i> Edit
</button>
<button class="text-xs text-indigo-600 delete-goal" data-id="${g.id}">
<i class="fas fa-trash mr-1"></i> Delete
</button>
</div>
`;
goalsList.appendChild(goalEl.cloneNode(true));
fullGoalsList.appendChild(goalEl);
});
// Add event listeners to goal buttons
document.querySelectorAll('.edit-goal').forEach(btn => {
btn.addEventListener('click', (e) => editGoal(e.target.getAttribute('data-id')));
});
document.querySelectorAll('.delete-goal').forEach(btn => {
btn.addEventListener('click', (e) => deleteGoal(e.target.getAttribute('data-id')));
});
// Update expense chart
updateExpenseChart();
}
function loadTransactions() {
const transactions = JSON.parse(localStorage.getItem('transactions'));
const settings = JSON.parse(localStorage.getItem('settings'));
allTransactions.innerHTML = '';
// Group transactions by date
const grouped = transactions.reduce((groups, t) => {
const date = t.date;
if (!groups[date]) {
groups[date] = [];
}
groups[date].push(t);
return groups;
}, {});
// Sort dates in descending order
const sortedDates = Object.keys(grouped).sort((a, b) => new Date(b) - new Date(a));
sortedDates.forEach(date => {
const dateHeader = document.createElement('div');
dateHeader.className = 'font-medium text-gray-500 mb-2';
dateHeader.textContent = formatDate(date);
allTransactions.appendChild(dateHeader);
grouped[date].forEach(t => {
const transactionEl = document.createElement('div');
transactionEl.className = 'flex justify-between items-center p-3 bg-gray-50 rounded-lg mb-2';
const iconMap = {
food: 'utensils',
transport: 'bus',
bills: 'file-invoice-dollar',
shopping: 'shopping-bag',
entertainment: 'film',
other: 'ellipsis-h'
};
transactionEl.innerHTML = `
<div class="flex items-center space-x-3">
<div class="p-2 rounded-full bg-${t.type === 'expense' ? 'red' : 'green'}-100 text-${t.type === 'expense' ? 'red' : 'green'}-600">
<i class="fas fa-${iconMap[t.category] || 'ellipsis-h'}"></i>
</div>
<div>
<p class="font-medium">${t.category.charAt(0).toUpperCase() + t.category.slice(1)}</p>
<p class="text-xs text-gray-500">${t.description || ''}</p>
</div>
</div>
<div class="text-right">
<p class="font-medium text-${t.type === 'expense' ? 'red' : 'green'}-600">${t.type === 'expense' ? '-' : '+'}${settings.currency}${Math.abs(t.amount).toFixed(2)}</p>
<div class="flex space-x-2 mt-1">
<button class="text-xs text-indigo-600 edit-transaction" data-id="${t.id}">
<i class="fas fa-edit"></i>
</button>
<button class="text-xs text-red-600 delete-transaction" data-id="${t.id}">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
`;
allTransactions.appendChild(transactionEl);
});
});
// Add event listeners to transaction buttons
document.querySelectorAll('.edit-transaction').forEach(btn => {
btn.addEventListener('click', (e) => editTransaction(e.target.getAttribute('data-id')));
});
document.querySelectorAll('.delete-transaction').forEach(btn => {
btn.addEventListener('click', (e) => deleteTransaction(e.target.getAttribute('data-id')));
});
}
function loadGoals() {
updateDashboard(); // Goals are already handled in updateDashboard
}
function loadBudgets() {
const budgets = JSON.parse(localStorage.getItem('budgets'));
const transactions = JSON.parse(localStorage.getItem('transactions'));
const settings = JSON.parse(localStorage.getItem('settings'));
budgetsList.innerHTML = '';
// Calculate current month's expenses per category
const currentMonth = new Date().getMonth();
const currentYear = new Date().getFullYear();
const monthlyExpensesByCategory = transactions
.filter(t => {
const date = new Date(t.date);
return date.getMonth() === currentMonth &&
date.getFullYear() === currentYear &&
t.type === 'expense';
})
.reduce((acc, t) => {
if (!acc[t.category]) {
acc[t.category] = 0;
}
acc[t.category] += Math.abs(t.amount);
return acc;
}, {});
budgets.forEach(b => {
const spent = monthlyExpensesByCategory[b.category] || 0;
const remaining = b.amount - spent;
const progress = (spent / b.amount) * 100;
const budgetEl = document.createElement('div');
budgetEl.className = 'bg-white rounded-lg shadow p-4';
budgetEl.innerHTML = `
<div class="flex justify-between items-center mb-2">
<h3 class="font-medium">${b.category.charAt(0).toUpperCase() + b.category.slice(1)}</h3>
<span class="text-sm">${settings.currency}${b.amount.toFixed(2)} ${b.period}</span>
</div>
<div class="flex justify-between text-sm mb-3">
<span>${settings.currency}${spent.toFixed(2)} spent</span>
<span class="${remaining < 0 ? 'text-red-500' : 'text-green-500'}">${remaining < 0 ? 'Over by ' + settings.currency + Math.abs(remaining).toFixed(2) : settings.currency + remaining.toFixed(2) + ' left'}</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div class="${progress > 100 ? 'bg-red-500' : 'bg-indigo-600'} h-2 rounded-full" style="width: ${Math.min(progress, 100)}%"></div>
</div>
<div class="flex justify-end mt-3">
<button class="text-xs text-indigo-600 delete-budget" data-category="${b.category}">
<i class="fas fa-trash mr-1"></i> Delete
</button>
</div>
`;
budgetsList.appendChild(budgetEl);
});
// Add event listeners to budget buttons
document.querySelectorAll('.delete-budget').forEach(btn => {
btn.addEventListener('click', (e) => deleteBudget(e.target.getAttribute('data-category')));
});
}
function updateExpenseChart() {
const transactions = JSON.parse(localStorage.getItem('transactions'));
const currentMonth = new Date().getMonth();
const currentYear = new Date().getFullYear();
// Filter current month's expenses
const monthlyExpenses = transactions.filter(t => {
const date = new Date(t.date);
return date.getMonth() === currentMonth &&
date.getFullYear() === currentYear &&
t.type === 'expense';
});
// Group by category
const expensesByCategory = monthlyExpenses.reduce((acc, t) => {
if (!acc[t.category]) {
acc[t.category] = 0;
}
acc[t.category] += Math.abs(t.amount);
return acc;
}, {});
const categories = Object.keys(expensesByCategory);
const amounts = Object.values(expensesByCategory);
const colors = [
'#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40'
];
// Destroy previous chart if exists
if (expenseChart) {
expenseChart.destroy();
}
expenseChart = new Chart(expenseChartCtx, {
type: 'pie',
data: {
labels: categories.map(c => c.charAt(0).toUpperCase() + c.slice(1)),
datasets: [{
data: amounts,
backgroundColor: colors,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right',
},
tooltip: {
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.raw || 0;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = Math.round((value / total) * 100);
return `${label}: ${percentage}% (${value.toFixed(2)})`;
}
}
}
}
}
});
}
function editTransaction(id) {
const transactions = JSON.parse(localStorage.getItem('transactions'));
const transaction = transactions.find(t => t.id == id);
if (!transaction) return;
// Fill the form
transactionAmount.value = Math.abs(transaction.amount);
transactionCategory.value = transaction.category;
transactionDescription.value = transaction.description || '';
transactionDate.value = transaction.date;
// Show modal
transactionModal.classList.remove('hidden');
// Remove the old transaction when saving
expenseBtn.onclick = function() {
const newTransaction = {
id: transaction.id,
amount: -parseFloat(transactionAmount.value),
category: transactionCategory.value,
description: transactionDescription.value,
date: transactionDate.value,
type: 'expense'
};
const updatedTransactions = transactions.map(t => t.id == id ? newTransaction : t);
localStorage.setItem('transactions', JSON.stringify(updatedTransactions));
transactionModal.classList.add('hidden');
updateDashboard();
loadTransactions();
};
incomeBtn.onclick = function() {
const newTransaction = {
id: transaction.id,
amount: parseFloat(transactionAmount.value),
category: transactionCategory.value,
description: transactionDescription.value,
date: transactionDate.value,
type: 'income'
};
const updatedTransactions = transactions.map(t => t.id == id ? newTransaction : t);
localStorage.setItem('transactions', JSON.stringify(updatedTransactions));
transactionModal.classList.add('hidden');
updateDashboard();
loadTransactions();
};
}
function deleteTransaction(id) {
if (confirm('Are you sure you want to delete this transaction?')) {
const transactions = JSON.parse(localStorage.getItem('transactions'));
const updatedTransactions = transactions.filter(t => t.id != id);
localStorage.setItem('transactions', JSON.stringify(updatedTransactions));
updateDashboard();
loadTransactions();
}
}
function editGoal(id) {
const goals = JSON.parse(localStorage.getItem('goals'));
const goal = goals.find(g => g.id == id);
if (!goal) return;
// Fill the form
goalName.value = goal.name;
goalTarget.value = goal.target;
goalCurrent.value = goal.current;
goalDate.value = goal.date;
// Show modal
goalModal.classList.remove('hidden');
// Update save button to edit instead of add
saveGoalBtn.onclick = function() {
const name = goalName.value;
const target = parseFloat(goalTarget.value);
const current = parseFloat(goalCurrent.value);
const date = goalDate.value;
if (!name || !target || target <= 0 || current < 0) {
alert('Please fill all required fields with valid values');
return;
}
const updatedGoal = {
id: goal.id,
name,
target,
current,
date
};
const updatedGoals = goals.map(g => g.id == id ? updatedGoal : g);
localStorage.setItem('goals', JSON.stringify(updatedGoals));
goalModal.classList.add('hidden');
updateDashboard();
};
}
function deleteGoal(id) {
if (confirm('Are you sure you want to delete this goal?')) {
const goals = JSON.parse(localStorage.getItem('goals'));
const updatedGoals = goals.filter(g => g.id != id);
localStorage.setItem('goals', JSON.stringify(updatedGoals));
updateDashboard();
}
}
function deleteBudget(category) {
if (confirm('Are you sure you want to delete this budget?')) {
const budgets = JSON.parse(localStorage.getItem('budgets'));
const updatedBudgets = budgets.filter(b => b.category != category);
localStorage.setItem('budgets', JSON.stringify(updatedBudgets));
loadBudgets();
}
}
function resetFilters() {
filterCategory.value = 'all';
filterType.value = 'all';
filterFromDate.value = '';
filterToDate.value = '';
}
function applyFilters() {
const category = filterCategory.value;
const type = filterType.value;
const fromDate = filterFromDate.value;
const toDate = filterToDate.value;
// Save filter settings
const settings = JSON.parse(localStorage.getItem('settings'));
settings.filters = { category, type, fromDate, toDate };
localStorage.setItem('settings', JSON.stringify(settings));
// Close modal
filterModal.classList.add('hidden');
// Reload transactions with filters
loadTransactions();
}
function applySort() {
const sortOption = document.querySelector('input[name="sortOption"]:checked').value;
// Save sort settings
const settings = JSON.parse(localStorage.getItem('settings'));
settings.sort = sortOption;
localStorage.setItem('settings', JSON.stringify(settings));
// Close modal
sortModal.classList.add('hidden');
// Reload transactions with sort
loadTransactions();
}
function exportJSON() {
const transactions = JSON.parse(localStorage.getItem('transactions'));
const goals = JSON.parse(localStorage.getItem('goals'));
const budgets = JSON.parse(localStorage.getItem('budgets'));
const data = {
transactions,
goals,
budgets,
exportedAt: new Date().toISOString()
};
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `fintrack-export-${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
exportModal.classList.add('hidden');
}
function exportCSV() {
const transactions = JSON.parse(localStorage.getItem('transactions'));
const goals = JSON.parse(localStorage.getItem('goals'));
const budgets = JSON.parse(localStorage.getItem('budgets'));
// Transactions CSV
let transactionsCSV = 'Type,Amount,Category,Description,Date\n';
transactions.forEach(t => {
transactionsCSV += `${t.type},${t.amount},${t.category},${t.description || ''},${t.date}\n`;
});
// Goals CSV
let goalsCSV = 'Name,Target,Current,Date\n';
goals.forEach(g => {
goalsCSV += `${g.name},${g.target},${g.current},${g.date}\n`;
});
// Budgets CSV
let budgetsCSV = 'Category,Amount,Period\n';
budgets.forEach(b => {
budgetsCSV += `${b.category},${b.amount},${b.period}\n`;
});
const fullCSV = `=== TRANSACTIONS ===\n${transactionsCSV}\n=== GOALS ===\n${goalsCSV}\n=== BUDGETS ===\n${budgetsCSV}`;
const blob = new Blob([fullCSV], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `fintrack-export-${new Date().toISOString().split('T')[0]}.csv`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
exportModal.classList.add('hidden');
}
function importData() {
const file = importFile.files[0];
if (!file) {
alert('Please select a file to import');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = JSON.parse(e.target.result);
if (confirm('This will overwrite your current data. Continue?')) {
if (data.transactions) {
localStorage.setItem('transactions', JSON.stringify(data.transactions));
}
if (data.goals) {
localStorage.setItem('goals', JSON.stringify(data.goals));
}
if (data.budgets) {
localStorage.setItem('budgets', JSON.stringify(data.budgets));
}
alert('Data imported successfully!');
initApp();
exportModal.classList.add('hidden');
}
} catch (error) {
alert('Error parsing the file. Please make sure it\'s a valid JSON export from FinTrack.');
}
};
reader.readAsText(file);
}
function toggleTheme() {
const settings = JSON.parse(localStorage.getItem('settings'));
if (document.body.classList.contains('dark')) {
document.body.classList.remove('dark');
themeToggle.innerHTML = '<i class="fas fa-moon"></i>';
settings.theme = 'light';
} else {
document.body.classList.add('dark');
themeToggle.innerHTML = '<i class="fas fa-sun"></i>';
settings.theme = 'dark';
}
localStorage.setItem('settings', JSON.stringify(settings));
}
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
</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=nameadarsh/trial-space" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>