Spaces:
Running
Running
| <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 ; | |
| } | |
| .dark .text-gray-800 { | |
| color: #e2e8f0 ; | |
| } | |
| .dark .border-gray-200 { | |
| border-color: #2d3748 ; | |
| } | |
| .dark .bg-indigo-600 { | |
| background-color: #4338ca ; | |
| } | |
| .dark .bg-indigo-100 { | |
| background-color: #312e81 ; | |
| } | |
| .dark .text-indigo-600 { | |
| color: #818cf8 ; | |
| } | |
| </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> |