Spaces:
Running
Running
Design a comprehensive application for personal finance management tailored for the user profile "Ben Mburu" with the slogan "GOD DID IT".The application should initiate tracking from June 24, 2025, in the currency of shillings.Requirements: 1.**User Profile**: - Name: Ben Mburu - Slogan: GOD DID IT 2.**Income and Expense Tracking**: - Categories for expenses: clothes, food, transport, etc.- Categories for income: salary, commission, tips, etc which should be Ksh. 0 for the start. Income Distribution Plan showing 50%, 30%, 20% allocation which the amount is Ksh. 0 for the start.- Functionality to input and categorize transactions easily.3.**Savings Plan**: - Options to set savings goals for: - Household products with a target of - Business capital - Emergencies, Savings Goals for Household (80k), Business (100k), and Emergency (20k) funds. 4.**Comparative Analysis**: - A dedicated section for a paragraph comparison of income versus expenses.- Ensure that savings and expenses do not exceed income.5.**User Interface Design**: - Incorporate various colors, specifically FBFEF9, 191923, 0E79B2, BF1363, F39237, and additional complementary colors for aesthetics.6.**To-Do List Feature**: - A section to create and manage a to-do list.- Each task should have a deadline.7.**Calendar Integration**: - A calendar to highlight events and important dates.8.**Monthly Reporting**: - Generate monthly reports summarizing income, expenses, and savings.overview, transactions, savings,analysis, todo list and calender should be in a seperate page or tab above arraged in arow. Each transaction made either income or expenses should update the whole app or system in every necessary category and keep the records. - Each report should include a paragraph illustrating the financial standing of the user.Ensure the application is user-friendly, visually appealing, and provides insightful financial analytics to help Ben Mburu manage his finances effectively. - Initial Deployment
57f3e27 verified | <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Ben Mburu's Finance Tracker | GOD DID IT</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <script> | |
| tailwind.config = { | |
| theme: { | |
| extend: { | |
| colors: { | |
| primary: '#FBFEF9', | |
| secondary: '#191923', | |
| accent1: '#0E79B2', | |
| accent2: '#BF1363', | |
| accent3: '#F39237', | |
| } | |
| } | |
| } | |
| } | |
| </script> | |
| <style> | |
| .tab-content { | |
| display: none; | |
| } | |
| .tab-content.active { | |
| display: block; | |
| animation: fadeIn 0.5s; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| .progress-bar { | |
| height: 10px; | |
| border-radius: 5px; | |
| transition: width 0.5s ease-in-out; | |
| } | |
| .calendar-day { | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .calendar-day:hover { | |
| transform: scale(1.05); | |
| } | |
| .calendar-day.has-event { | |
| background-color: rgba(14, 121, 178, 0.2); | |
| } | |
| .calendar-day.today { | |
| border: 2px solid #F39237; | |
| } | |
| .transaction-item { | |
| transition: all 0.3s; | |
| } | |
| .transaction-item:hover { | |
| transform: translateX(5px); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-primary text-secondary min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <!-- Header --> | |
| <header class="bg-accent1 text-primary rounded-lg p-6 mb-8 shadow-lg"> | |
| <div class="flex flex-col md:flex-row justify-between items-center"> | |
| <div> | |
| <h1 class="text-3xl font-bold">Ben Mburu's Finance Tracker</h1> | |
| <p class="text-accent3 italic text-xl">GOD DID IT</p> | |
| <p class="mt-2">Tracking since: June 24, 2025</p> | |
| </div> | |
| <div class="mt-4 md:mt-0 bg-accent2 p-3 rounded-full"> | |
| <p class="text-xl font-bold">Balance: <span id="totalBalance">0</span> Ksh</p> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Navigation Tabs --> | |
| <div class="flex overflow-x-auto mb-8 border-b-2 border-accent2"> | |
| <button class="tab-btn px-6 py-3 font-medium bg-accent1 text-primary rounded-t-lg mr-2" data-tab="overview">Overview</button> | |
| <button class="tab-btn px-6 py-3 font-medium hover:bg-accent1 hover:text-primary rounded-t-lg mr-2" data-tab="transactions">Transactions</button> | |
| <button class="tab-btn px-6 py-3 font-medium hover:bg-accent1 hover:text-primary rounded-t-lg mr-2" data-tab="savings">Savings</button> | |
| <button class="tab-btn px-6 py-3 font-medium hover:bg-accent1 hover:text-primary rounded-t-lg mr-2" data-tab="analysis">Analysis</button> | |
| <button class="tab-btn px-6 py-3 font-medium hover:bg-accent1 hover:text-primary rounded-t-lg mr-2" data-tab="todo">To-Do List</button> | |
| <button class="tab-btn px-6 py-3 font-medium hover:bg-accent1 hover:text-primary rounded-t-lg" data-tab="calendar">Calendar</button> | |
| </div> | |
| <!-- Overview Tab --> | |
| <div id="overview" class="tab-content active p-6 bg-white rounded-lg shadow-md"> | |
| <h2 class="text-2xl font-bold mb-6 text-accent2">Financial Overview</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8"> | |
| <!-- Income Card --> | |
| <div class="bg-green-50 p-4 rounded-lg border-l-4 border-green-500"> | |
| <h3 class="font-semibold text-lg mb-2">Total Income</h3> | |
| <p class="text-2xl font-bold" id="totalIncome">0 Ksh</p> | |
| <div class="mt-4"> | |
| <h4 class="font-medium mb-1">Income Distribution</h4> | |
| <div class="bg-gray-200 h-2 rounded-full mb-1"> | |
| <div class="bg-green-500 h-2 rounded-full progress-bar" style="width: 50%" id="needsBar"></div> | |
| </div> | |
| <p>Needs (50%): <span id="needsAmount">0</span> Ksh</p> | |
| <div class="bg-gray-200 h-2 rounded-full mb-1 mt-2"> | |
| <div class="bg-blue-500 h-2 rounded-full progress-bar" style="width: 30%" id="wantsBar"></div> | |
| </div> | |
| <p>Wants (30%): <span id="wantsAmount">0</span> Ksh</p> | |
| <div class="bg-gray-200 h-2 rounded-full mb-1 mt-2"> | |
| <div class="bg-purple-500 h-2 rounded-full progress-bar" style="width: 20%" id="savingsBar"></div> | |
| </div> | |
| <p>Savings (20%): <span id="savingsAmount">0</span> Ksh</p> | |
| </div> | |
| </div> | |
| <!-- Expenses Card --> | |
| <div class="bg-red-50 p-4 rounded-lg border-l-4 border-red-500"> | |
| <h3 class="font-semibold text-lg mb-2">Total Expenses</h3> | |
| <p class="text-2xl font-bold" id="totalExpenses">0 Ksh</p> | |
| <div class="mt-4"> | |
| <h4 class="font-medium mb-2">Expense Categories</h4> | |
| <div id="expenseCategoriesOverview"> | |
| <p class="text-gray-600">No expenses recorded yet</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Savings Progress Card --> | |
| <div class="bg-blue-50 p-4 rounded-lg border-l-4 border-blue-500"> | |
| <h3 class="font-semibold text-lg mb-2">Savings Progress</h3> | |
| <div class="space-y-4"> | |
| <div> | |
| <h4 class="font-medium">Household (80k)</h4> | |
| <div class="bg-gray-200 h-2 rounded-full mb-1"> | |
| <div class="bg-accent2 h-2 rounded-full progress-bar" style="width: 0%" id="householdBar"></div> | |
| </div> | |
| <p><span id="householdSaved">0</span> Ksh saved (<span id="householdPercent">0</span>%)</p> | |
| </div> | |
| <div> | |
| <h4 class="font-medium">Business (100k)</h4> | |
| <div class="bg-gray-200 h-2 rounded-full mb-1"> | |
| <div class="bg-accent3 h-2 rounded-full progress-bar" style="width: 0%" id="businessBar"></div> | |
| </div> | |
| <p><span id="businessSaved">0</span> Ksh saved (<span id="businessPercent">0</span>%)</p> | |
| </div> | |
| <div> | |
| <h4 class="font-medium">Emergency (20k)</h4> | |
| <div class="bg-gray-200 h-2 rounded-full mb-1"> | |
| <div class="bg-accent2 h-2 rounded-full progress-bar" style="width: 0%" id="emergencyBar"></div> | |
| </div> | |
| <p><span id="emergencySaved">0</span> Ksh saved (<span id="emergencyPercent">0</span>%)</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-accent3 bg-opacity-10 p-4 rounded-lg"> | |
| <h3 class="font-semibold text-lg mb-2">Financial Status</h3> | |
| <p id="financialStatusText">You haven't recorded any transactions yet. Start by adding your income and expenses to get a clear picture of your financial health.</p> | |
| </div> | |
| </div> | |
| <!-- Transactions Tab --> | |
| <div id="transactions" class="tab-content p-6 bg-white rounded-lg shadow-md"> | |
| <h2 class="text-2xl font-bold mb-6 text-accent2">Record Transactions</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-8"> | |
| <!-- Add Transaction Form --> | |
| <div> | |
| <div class="bg-gray-50 p-4 rounded-lg mb-6"> | |
| <h3 class="font-semibold text-lg mb-4">Add New Transaction</h3> | |
| <div class="mb-4"> | |
| <label class="block text-sm font-medium mb-1">Transaction Type</label> | |
| <select id="transactionType" class="w-full p-2 border rounded"> | |
| <option value="income">Income</option> | |
| <option value="expense">Expense</option> | |
| </select> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-sm font-medium mb-1">Category</label> | |
| <select id="transactionCategory" class="w-full p-2 border rounded"> | |
| <!-- Income categories --> | |
| <option value="salary" class="income-option">Salary</option> | |
| <option value="commission" class="income-option">Commission</option> | |
| <option value="tips" class="income-option">Tips</option> | |
| <!-- Expense categories --> | |
| <option value="clothes" class="expense-option" style="display:none">Clothes</option> | |
| <option value="food" class="expense-option" style="display:none">Food</option> | |
| <option value="transport" class="expense-option" style="display:none">Transport</option> | |
| <option value="housing" class="expense-option" style="display:none">Housing</option> | |
| <option value="utilities" class="expense-option" style="display:none">Utilities</option> | |
| <option value="entertainment" class="expense-option" style="display:none">Entertainment</option> | |
| </select> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-sm font-medium mb-1">Amount (Ksh)</label> | |
| <input type="number" id="transactionAmount" class="w-full p-2 border rounded" placeholder="0"> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-sm font-medium mb-1">Date</label> | |
| <input type="date" id="transactionDate" class="w-full p-2 border rounded"> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-sm font-medium mb-1">Description (Optional)</label> | |
| <textarea id="transactionDescription" class="w-full p-2 border rounded" rows="2"></textarea> | |
| </div> | |
| <button id="addTransactionBtn" class="bg-accent1 text-white py-2 px-4 rounded hover:bg-blue-700 transition"> | |
| Add Transaction | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Recent Transactions --> | |
| <div> | |
| <h3 class="font-semibold text-lg mb-4">Recent Transactions</h3> | |
| <div class="mb-4"> | |
| <input type="text" id="transactionSearch" class="w-full p-2 border rounded mb-2" placeholder="Search transactions..."> | |
| <div class="flex space-x-2"> | |
| <select id="transactionFilterType" class="p-2 border rounded"> | |
| <option value="all">All Types</option> | |
| <option value="income">Income</option> | |
| <option value="expense">Expense</option> | |
| </select> | |
| <select id="transactionFilterCategory" class="p-2 border rounded"> | |
| <option value="all">All Categories</option> | |
| <!-- Will be populated by JavaScript --> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="max-h-96 overflow-y-auto"> | |
| <table class="w-full"> | |
| <thead class="bg-gray-100"> | |
| <tr> | |
| <th class="p-2 text-left">Date</th> | |
| <th class="p-2 text-left">Description</th> | |
| <th class="p-2 text-left">Category</th> | |
| <th class="p-2 text-right">Amount</th> | |
| <th class="p-2 text-right">Action</th> | |
| </tr> | |
| </thead> | |
| <tbody id="transactionsList"> | |
| <tr> | |
| <td colspan="5" class="p-4 text-center text-gray-500">No transactions recorded yet</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Savings Tab --> | |
| <div id="savings" class="tab-content p-6 bg-white rounded-lg shadow-md"> | |
| <h2 class="text-2xl font-bold mb-6 text-accent2">Savings Goals</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8"> | |
| <!-- Household Savings --> | |
| <div class="bg-purple-50 p-4 rounded-lg border-l-4 border-purple-500"> | |
| <h3 class="font-semibold text-lg mb-2">Household Products</h3> | |
| <p class="text-sm text-gray-600 mb-2">Target: 80,000 Ksh</p> | |
| <div class="bg-gray-200 h-4 rounded-full mb-2"> | |
| <div class="bg-purple-500 h-4 rounded-full progress-bar" id="householdProgressBar" style="width: 0%"></div> | |
| </div> | |
| <p class="text-lg font-bold"><span id="householdSavedAmount">0</span> Ksh saved (<span id="householdPercentage">0</span>%)</p> | |
| <div class="mt-4"> | |
| <label class="block text-sm font-medium mb-1">Add to Savings</label> | |
| <div class="flex"> | |
| <input type="number" id="householdAddAmount" class="flex-1 p-2 border rounded-l" placeholder="Amount"> | |
| <button class="bg-purple-500 text-white px-3 rounded-r" onclick="addToSavings('household')">Add</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Business Savings --> | |
| <div class="bg-green-50 p-4 rounded-lg border-l-4 border-green-500"> | |
| <h3 class="font-semibold text-lg mb-2">Business Capital</h3> | |
| <p class="text-sm text-gray-600 mb-2">Target: 100,000 Ksh</p> | |
| <div class="bg-gray-200 h-4 rounded-full mb-2"> | |
| <div class="bg-green-500 h-4 rounded-full progress-bar" id="businessProgressBar" style="width: 0%"></div> | |
| </div> | |
| <p class="text-lg font-bold"><span id="businessSavedAmount">0</span> Ksh saved (<span id="businessPercentage">0</span>%)</p> | |
| <div class="mt-4"> | |
| <label class="block text-sm font-medium mb-1">Add to Savings</label> | |
| <div class="flex"> | |
| <input type="number" id="businessAddAmount" class="flex-1 p-2 border rounded-l" placeholder="Amount"> | |
| <button class="bg-green-500 text-white px-3 rounded-r" onclick="addToSavings('business')">Add</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Emergency Savings --> | |
| <div class="bg-red-50 p-4 rounded-lg border-l-4 border-red-500"> | |
| <h3 class="font-semibold text-lg mb-2">Emergency Fund</h3> | |
| <p class="text-sm text-gray-600 mb-2">Target: 20,000 Ksh</p> | |
| <div class="bg-gray-200 h-4 rounded-full mb-2"> | |
| <div class="bg-red-500 h-4 rounded-full progress-bar" id="emergencyProgressBar" style="width: 0%"></div> | |
| </div> | |
| <p class="text-lg font-bold"><span id="emergencySavedAmount">0</span> Ksh saved (<span id="emergencyPercentage">0</span>%)</p> | |
| <div class="mt-4"> | |
| <label class="block text-sm font-medium mb-1">Add to Savings</label> | |
| <div class="flex"> | |
| <input type="number" id="emergencyAddAmount" class="flex-1 p-2 border rounded-l" placeholder="Amount"> | |
| <button class="bg-red-500 text-white px-3 rounded-r" onclick="addToSavings('emergency')">Add</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <h3 class="font-semibold text-lg mb-4">Savings History</h3> | |
| <div class="overflow-x-auto"> | |
| <table class="w-full"> | |
| <thead class="bg-gray-100"> | |
| <tr> | |
| <th class="p-2 text-left">Date</th> | |
| <th class="p-2 text-left">Goal</th> | |
| <th class="p-2 text-right">Amount</th> | |
| <th class="p-2 text-right">Balance</th> | |
| </tr> | |
| </thead> | |
| <tbody id="savingsHistory"> | |
| <tr> | |
| <td colspan="4" class="p-4 text-center text-gray-500">No savings recorded yet</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Analysis Tab --> | |
| <div id="analysis" class="tab-content p-6 bg-white rounded-lg shadow-md"> | |
| <h2 class="text-2xl font-bold mb-6 text-accent2">Financial Analysis</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-8"> | |
| <!-- Income vs Expenses Chart --> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <h3 class="font-semibold text-lg mb-4">Income vs Expenses</h3> | |
| <div class="h-64 flex items-end space-x-2" id="incomeExpenseChart"> | |
| <div class="flex-1 flex flex-col items-center"> | |
| <div class="bg-green-500 w-full rounded-t" id="incomeBar" style="height: 0%"></div> | |
| <p class="mt-2 text-sm">Income</p> | |
| </div> | |
| <div class="flex-1 flex flex-col items-center"> | |
| <div class="bg-red-500 w-full rounded-t" id="expenseBar" style="height: 0%"></div> | |
| <p class="mt-2 text-sm">Expenses</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Expense Breakdown --> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <h3 class="font-semibold text-lg mb-4">Expense Breakdown</h3> | |
| <div class="h-64 flex items-center justify-center" id="expensePieChart"> | |
| <p class="text-gray-500">No expenses to display</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Financial Analysis Text --> | |
| <div class="bg-accent1 bg-opacity-10 p-4 rounded-lg"> | |
| <h3 class="font-semibold text-lg mb-2">Financial Analysis Report</h3> | |
| <p id="financialAnalysisText">You haven't recorded enough transactions to generate a financial analysis. Start by adding your income and expenses to get valuable insights into your financial habits.</p> | |
| </div> | |
| <!-- Monthly Report --> | |
| <div class="mt-8 bg-white border rounded-lg p-4 shadow"> | |
| <h3 class="font-semibold text-lg mb-4">Generate Monthly Report</h3> | |
| <div class="flex items-center space-x-4 mb-4"> | |
| <select id="reportMonth" class="p-2 border rounded"> | |
| <option value="1">January</option> | |
| <option value="2">February</option> | |
| <option value="3">March</option> | |
| <option value="4">April</option> | |
| <option value="5">May</option> | |
| <option value="6">June</option> | |
| <option value="7">July</option> | |
| <option value="8">August</option> | |
| <option value="9">September</option> | |
| <option value="10">October</option> | |
| <option value="11">November</option> | |
| <option value="12">December</option> | |
| </select> | |
| <select id="reportYear" class="p-2 border rounded"> | |
| <option value="2025">2025</option> | |
| <option value="2026">2026</option> | |
| </select> | |
| <button id="generateReportBtn" class="bg-accent2 text-white py-2 px-4 rounded hover:bg-gray-800 transition"> | |
| Generate Report | |
| </button> | |
| </div> | |
| <div id="monthlyReport" class="hidden bg-gray-50 p-4 rounded-lg"> | |
| <h4 class="font-semibold mb-2">Monthly Report - <span id="reportTitle">June 2025</span></h4> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4"> | |
| <div class="bg-green-50 p-2 rounded"> | |
| <p class="font-medium">Total Income:</p> | |
| <p class="text-xl" id="reportIncome">0 Ksh</p> | |
| </div> | |
| <div class="bg-red-50 p-2 rounded"> | |
| <p class="font-medium">Total Expenses:</p> | |
| <p class="text-xl" id="reportExpenses">0 Ksh</p> | |
| </div> | |
| <div class="bg-blue-50 p-2 rounded"> | |
| <p class="font-medium">Net Savings:</p> | |
| <p class="text-xl" id="reportSavings">0 Ksh</p> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <h5 class="font-medium mb-2">Top Expense Categories:</h5> | |
| <div id="reportTopExpenses"> | |
| <p class="text-gray-600">No expense data available</p> | |
| </div> | |
| </div> | |
| <div> | |
| <h5 class="font-medium mb-2">Financial Summary:</h5> | |
| <p id="reportSummary">No financial data available for this month.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- To-Do List Tab --> | |
| <div id="todo" class="tab-content p-6 bg-white rounded-lg shadow-md"> | |
| <h2 class="text-2xl font-bold mb-6 text-accent2">To-Do List</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-8"> | |
| <!-- Add Task Form --> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <h3 class="font-semibold text-lg mb-4">Add New Task</h3> | |
| <div class="mb-4"> | |
| <label class="block text-sm font-medium mb-1">Task Title</label> | |
| <input type="text" id="taskTitle" class="w-full p-2 border rounded" placeholder="What needs to be done?"> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-sm font-medium mb-1">Priority</label> | |
| <select id="taskPriority" class="w-full p-2 border rounded"> | |
| <option value="low">Low</option> | |
| <option value="medium">Medium</option> | |
| <option value="high">High</option> | |
| </select> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-sm font-medium mb-1">Deadline</label> | |
| <input type="date" id="taskDeadline" class="w-full p-2 border rounded"> | |
| </div> | |
| <div class="mb-4"> | |
| <label class="block text-sm font-medium mb-1">Description (Optional)</label> | |
| <textarea id="taskDescription" class="w-full p-2 border rounded" rows="3"></textarea> | |
| </div> | |
| <button id="addTaskBtn" class="bg-accent2 text-white py-2 px-4 rounded hover:bg-gray-800 transition"> | |
| Add Task | |
| </button> | |
| </div> | |
| <!-- Task List --> | |
| <div> | |
| <h3 class="font-semibold text-lg mb-4">Your Tasks</h3> | |
| <div class="mb-4 flex space-x-2"> | |
| <select id="taskFilterStatus" class="p-2 border rounded"> | |
| <option value="all">All Tasks</option> | |
| <option value="pending">Pending</option> | |
| <option value="completed">Completed</option> | |
| </select> | |
| <select id="taskFilterPriority" class="p-2 border rounded"> | |
| <option value="all">All Priorities</option> | |
| <option value="low">Low</option> | |
| <option value="medium">Medium</option> | |
| <option value="high">High</option> | |
| </select> | |
| </div> | |
| <div class="max-h-96 overflow-y-auto"> | |
| <ul id="taskList"> | |
| <li class="p-4 text-center text-gray-500">No tasks added yet</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Calendar Tab --> | |
| <div id="calendar" class="tab-content p-6 bg-white rounded-lg shadow-md"> | |
| <h2 class="text-2xl font-bold mb-6 text-accent2">Calendar</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-8"> | |
| <!-- Calendar --> | |
| <div class="md:col-span-2"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="font-semibold text-lg" id="calendarMonthYear">June 2025</h3> | |
| <div class="flex space-x-2"> | |
| <button id="prevMonthBtn" class="p-2 rounded-full hover:bg-gray-100"> | |
| <i class="fas fa-chevron-left"></i> | |
| </button> | |
| <button id="todayBtn" class="p-2 rounded-full hover:bg-gray-100">Today</button> | |
| <button id="nextMonthBtn" class="p-2 rounded-full hover:bg-gray-100"> | |
| <i class="fas fa-chevron-right"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="bg-white rounded-lg shadow overflow-hidden"> | |
| <div class="grid grid-cols-7 bg-gray-100"> | |
| <div class="p-2 text-center font-medium text-sm">Sun</div> | |
| <div class="p-2 text-center font-medium text-sm">Mon</div> | |
| <div class="p-2 text-center font-medium text-sm">Tue</div> | |
| <div class="p-2 text-center font-medium text-sm">Wed</div> | |
| <div class="p-2 text-center font-medium text-sm">Thu</div> | |
| <div class="p-2 text-center font-medium text-sm">Fri</div> | |
| <div class="p-2 text-center font-medium text-sm">Sat</div> | |
| </div> | |
| <div class="grid grid-cols-7" id="calendarDays"> | |
| <!-- Calendar days will be populated by JavaScript --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Events for Selected Day --> | |
| <div> | |
| <h3 class="font-semibold text-lg mb-4" id="selectedDayTitle">No day selected</h3> | |
| <div class="mb-4 bg-gray-50 p-4 rounded-lg"> | |
| <h4 class="font-medium mb-2">Add Event</h4> | |
| <div class="mb-2"> | |
| <label class="block text-sm font-medium mb-1">Event Title</label> | |
| <input type="text" id="eventTitle" class="w-full p-2 border rounded"> | |
| </div> | |
| <div class="mb-2"> | |
| <label class="block text-sm font-medium mb-1">Time</label> | |
| <input type="time" id="eventTime" class="w-full p-2 border rounded"> | |
| </div> | |
| <div class="mb-2"> | |
| <label class="block text-sm font-medium mb-1">Description (Optional)</label> | |
| <textarea id="eventDescription" class="w-full p-2 border rounded" rows="2"></textarea> | |
| </div> | |
| <button id="addEventBtn" class="bg-accent2 text-white py-1 px-3 rounded text-sm hover:bg-gray-800 transition"> | |
| Add Event | |
| </button> | |
| </div> | |
| <div> | |
| <h4 class="font-medium mb-2">Events</h4> | |
| <ul id="dayEventsList"> | |
| <li class="text-gray-500">No events scheduled</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Initialize data | |
| let financeData = { | |
| transactions: [], | |
| savings: { | |
| household: { saved: 0, target: 80000, history: [] }, | |
| business: { saved: 0, target: 100000, history: [] }, | |
| emergency: { saved: 0, target: 20000, history: [] } | |
| }, | |
| todoList: [], | |
| calendarEvents: {}, | |
| currentDate: new Date(2025, 5, 24) // June 24, 2025 | |
| }; | |
| // DOM Elements | |
| const tabButtons = document.querySelectorAll('.tab-btn'); | |
| const tabContents = document.querySelectorAll('.tab-content'); | |
| const transactionTypeSelect = document.getElementById('transactionType'); | |
| const transactionCategorySelect = document.getElementById('transactionCategory'); | |
| const transactionAmountInput = document.getElementById('transactionAmount'); | |
| const transactionDateInput = document.getElementById('transactionDate'); | |
| const transactionDescriptionInput = document.getElementById('transactionDescription'); | |
| const addTransactionBtn = document.getElementById('addTransactionBtn'); | |
| const transactionsList = document.getElementById('transactionsList'); | |
| const transactionSearchInput = document.getElementById('transactionSearch'); | |
| const transactionFilterType = document.getElementById('transactionFilterType'); | |
| const transactionFilterCategory = document.getElementById('transactionFilterCategory'); | |
| const totalBalanceElement = document.getElementById('totalBalance'); | |
| const totalIncomeElement = document.getElementById('totalIncome'); | |
| const totalExpensesElement = document.getElementById('totalExpenses'); | |
| const financialStatusText = document.getElementById('financialStatusText'); | |
| const needsAmountElement = document.getElementById('needsAmount'); | |
| const wantsAmountElement = document.getElementById('wantsAmount'); | |
| const savingsAmountElement = document.getElementById('savingsAmount'); | |
| const needsBar = document.getElementById('needsBar'); | |
| const wantsBar = document.getElementById('wantsBar'); | |
| const savingsBar = document.getElementById('savingsBar'); | |
| const expenseCategoriesOverview = document.getElementById('expenseCategoriesOverview'); | |
| const householdSavedElement = document.getElementById('householdSaved'); | |
| const householdPercentElement = document.getElementById('householdPercent'); | |
| const businessSavedElement = document.getElementById('businessSaved'); | |
| const businessPercentElement = document.getElementById('businessPercent'); | |
| const emergencySavedElement = document.getElementById('emergencySaved'); | |
| const emergencyPercentElement = document.getElementById('emergencyPercent'); | |
| const householdBarElement = document.getElementById('householdBar'); | |
| const businessBarElement = document.getElementById('businessBar'); | |
| const emergencyBarElement = document.getElementById('emergencyBar'); | |
| const incomeBar = document.getElementById('incomeBar'); | |
| const expenseBar = document.getElementById('expenseBar'); | |
| const expensePieChart = document.getElementById('expensePieChart'); | |
| const financialAnalysisText = document.getElementById('financialAnalysisText'); | |
| const reportMonthSelect = document.getElementById('reportMonth'); | |
| const reportYearSelect = document.getElementById('reportYear'); | |
| const generateReportBtn = document.getElementById('generateReportBtn'); | |
| const monthlyReport = document.getElementById('monthlyReport'); | |
| const reportTitle = document.getElementById('reportTitle'); | |
| const reportIncome = document.getElementById('reportIncome'); | |
| const reportExpenses = document.getElementById('reportExpenses'); | |
| const reportSavings = document.getElementById('reportSavings'); | |
| const reportTopExpenses = document.getElementById('reportTopExpenses'); | |
| const reportSummary = document.getElementById('reportSummary'); | |
| const taskTitleInput = document.getElementById('taskTitle'); | |
| const taskPrioritySelect = document.getElementById('taskPriority'); | |
| const taskDeadlineInput = document.getElementById('taskDeadline'); | |
| const taskDescriptionInput = document.getElementById('taskDescription'); | |
| const addTaskBtn = document.getElementById('addTaskBtn'); | |
| const taskList = document.getElementById('taskList'); | |
| const taskFilterStatus = document.getElementById('taskFilterStatus'); | |
| const taskFilterPriority = document.getElementById('taskFilterPriority'); | |
| const calendarMonthYear = document.getElementById('calendarMonthYear'); | |
| const prevMonthBtn = document.getElementById('prevMonthBtn'); | |
| const todayBtn = document.getElementById('todayBtn'); | |
| const nextMonthBtn = document.getElementById('nextMonthBtn'); | |
| const calendarDays = document.getElementById('calendarDays'); | |
| const selectedDayTitle = document.getElementById('selectedDayTitle'); | |
| const eventTitleInput = document.getElementById('eventTitle'); | |
| const eventTimeInput = document.getElementById('eventTime'); | |
| const eventDescriptionInput = document.getElementById('eventDescription'); | |
| const addEventBtn = document.getElementById('addEventBtn'); | |
| const dayEventsList = document.getElementById('dayEventsList'); | |
| // Initialize date for transaction | |
| transactionDateInput.valueAsDate = new Date(); | |
| // Tab switching | |
| tabButtons.forEach(button => { | |
| button.addEventListener('click', () => { | |
| const tabId = button.getAttribute('data-tab'); | |
| // Update active tab button | |
| tabButtons.forEach(btn => { | |
| btn.classList.remove('bg-accent1', 'text-primary'); | |
| btn.classList.add('hover:bg-accent1', 'hover:text-primary'); | |
| }); | |
| button.classList.add('bg-accent1', 'text-primary'); | |
| button.classList.remove('hover:bg-accent1', 'hover:text-primary'); | |
| // Update active tab content | |
| tabContents.forEach(content => { | |
| content.classList.remove('active'); | |
| }); | |
| document.getElementById(tabId).classList.add('active'); | |
| // Special cases | |
| if (tabId === 'calendar') { | |
| renderCalendar(); | |
| } | |
| }); | |
| }); | |
| // Transaction type change | |
| transactionTypeSelect.addEventListener('change', () => { | |
| const type = transactionTypeSelect.value; | |
| const incomeOptions = document.querySelectorAll('.income-option'); | |
| const expenseOptions = document.querySelectorAll('.expense-option'); | |
| if (type === 'income') { | |
| incomeOptions.forEach(option => { | |
| option.style.display = 'block'; | |
| }); | |
| expenseOptions.forEach(option => { | |
| option.style.display = 'none'; | |
| }); | |
| } else { | |
| incomeOptions.forEach(option => { | |
| option.style.display = 'none'; | |
| }); | |
| expenseOptions.forEach(option => { | |
| option.style.display = 'block'; | |
| }); | |
| } | |
| // Reset category to first available | |
| transactionCategorySelect.selectedIndex = 0; | |
| }); | |
| // Add transaction | |
| addTransactionBtn.addEventListener('click', () => { | |
| const type = transactionTypeSelect.value; | |
| const category = transactionCategorySelect.value; | |
| const amount = parseFloat(transactionAmountInput.value); | |
| const date = transactionDateInput.value; | |
| const description = transactionDescriptionInput.value.trim(); | |
| if (!amount || amount <= 0) { | |
| alert('Please enter a valid amount'); | |
| return; | |
| } | |
| if (!date) { | |
| alert('Please select a date'); | |
| return; | |
| } | |
| const transaction = { | |
| id: Date.now(), | |
| type, | |
| category, | |
| amount, | |
| date, | |
| description, | |
| timestamp: new Date().toISOString() | |
| }; | |
| financeData.transactions.push(transaction); | |
| saveData(); | |
| renderTransactions(); | |
| updateFinancialOverview(); | |
| // Reset form | |
| transactionAmountInput.value = ''; | |
| transactionDescriptionInput.value = ''; | |
| transactionDateInput.valueAsDate = new Date(); | |
| }); | |
| // Filter transactions | |
| transactionSearchInput.addEventListener('input', renderTransactions); | |
| transactionFilterType.addEventListener('change', renderTransactions); | |
| transactionFilterCategory.addEventListener('change', renderTransactions); | |
| // Render transactions | |
| function renderTransactions() { | |
| const searchTerm = transactionSearchInput.value.toLowerCase(); | |
| const filterType = transactionFilterType.value; | |
| const filterCategory = transactionFilterCategory.value; | |
| // Update category filter options | |
| updateCategoryFilterOptions(); | |
| const filteredTransactions = financeData.transactions.filter(transaction => { | |
| const matchesSearch = transaction.description.toLowerCase().includes(searchTerm) || | |
| transaction.category.toLowerCase().includes(searchTerm) || | |
| transaction.amount.toString().includes(searchTerm); | |
| const matchesType = filterType === 'all' || transaction.type === filterType; | |
| const matchesCategory = filterCategory === 'all' || transaction.category === filterCategory; | |
| return matchesSearch && matchesType && matchesCategory; | |
| }); | |
| if (filteredTransactions.length === 0) { | |
| transactionsList.innerHTML = ` | |
| <tr> | |
| <td colspan="5" class="p-4 text-center text-gray-500">No transactions found</td> | |
| </tr> | |
| `; | |
| return; | |
| } | |
| transactionsList.innerHTML = ''; | |
| filteredTransactions.sort((a, b) => new Date(b.date) - new Date(a.date)).forEach(transaction => { | |
| const row = document.createElement('tr'); | |
| row.className = 'transaction-item border-b hover:bg-gray-50'; | |
| row.innerHTML = ` | |
| <td class="p-2">${formatDate(transaction.date)}</td> | |
| <td class="p-2">${transaction.description || 'No description'}</td> | |
| <td class="p-2 capitalize">${transaction.category}</td> | |
| <td class="p-2 text-right font-medium ${transaction.type === 'income' ? 'text-green-600' : 'text-red-600'}"> | |
| ${transaction.type === 'income' ? '+' : '-'}${transaction.amount.toLocaleString()} Ksh | |
| </td> | |
| <td class="p-2 text-right"> | |
| <button class="text-red-500 hover:text-red-700" onclick="deleteTransaction(${transaction.id})"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </td> | |
| `; | |
| transactionsList.appendChild(row); | |
| }); | |
| } | |
| // Update category filter options | |
| function updateCategoryFilterOptions() { | |
| const currentCategories = new Set(); | |
| financeData.transactions.forEach(t => currentCategories.add(t.category)); | |
| const currentValue = transactionFilterCategory.value; | |
| transactionFilterCategory.innerHTML = ` | |
| <option value="all">All Categories</option> | |
| ${Array.from(currentCategories).map(cat => | |
| `<option value="${cat}" ${cat === currentValue ? 'selected' : ''}>${capitalizeFirstLetter(cat)}</option>` | |
| ).join('')} | |
| `; | |
| } | |
| // Delete transaction | |
| function deleteTransaction(id) { | |
| if (confirm('Are you sure you want to delete this transaction?')) { | |
| financeData.transactions = financeData.transactions.filter(t => t.id !== id); | |
| saveData(); | |
| renderTransactions(); | |
| updateFinancialOverview(); | |
| } | |
| } | |
| // Update financial overview | |
| function updateFinancialOverview() { | |
| // Calculate totals | |
| const totalIncome = financeData.transactions | |
| .filter(t => t.type === 'income') | |
| .reduce((sum, t) => sum + t.amount, 0); | |
| const totalExpenses = financeData.transactions | |
| .filter(t => t.type === 'expense') | |
| .reduce((sum, t) => sum + t.amount, 0); | |
| const totalSavings = financeData.savings.household.saved + | |
| financeData.savings.business.saved + | |
| financeData.savings.emergency.saved; | |
| const balance = totalIncome - totalExpenses - totalSavings; | |
| // Update UI | |
| totalBalanceElement.textContent = balance.toLocaleString(); | |
| totalIncomeElement.textContent = totalIncome.toLocaleString() + ' Ksh'; | |
| totalExpensesElement.textContent = totalExpenses.toLocaleString() + ' Ksh'; | |
| // Update income distribution | |
| const needsAmount = totalIncome * 0.5; | |
| const wantsAmount = totalIncome * 0.3; | |
| const savingsAmount = totalIncome * 0.2; | |
| needsAmountElement.textContent = needsAmount.toLocaleString(); | |
| wantsAmountElement.textContent = wantsAmount.toLocaleString(); | |
| savingsAmountElement.textContent = savingsAmount.toLocaleString(); | |
| needsBar.style.width = '50%'; | |
| wantsBar.style.width = '30%'; | |
| savingsBar.style.width = '20%'; | |
| // Update expense categories overview | |
| const expenseCategories = {}; | |
| financeData.transactions | |
| .filter(t => t.type === 'expense') | |
| .forEach(t => { | |
| expenseCategories[t.category] = (expenseCategories[t.category] || 0) + t.amount; | |
| }); | |
| if (Object.keys(expenseCategories).length === 0) { | |
| expenseCategoriesOverview.innerHTML = '<p class="text-gray-600">No expenses recorded yet</p>'; | |
| } else { | |
| expenseCategoriesOverview.innerHTML = Object.entries(expenseCategories) | |
| .sort((a, b) => b[1] - a[1]) | |
| .map(([category, amount]) => ` | |
| <div class="flex justify-between mb-1"> | |
| <span class="capitalize">${category}</span> | |
| <span class="font-medium">${amount.toLocaleString()} Ksh</span> | |
| </div> | |
| `).join(''); | |
| } | |
| // Update savings progress | |
| updateSavingsProgress(); | |
| // Update financial status text | |
| updateFinancialStatusText(totalIncome, totalExpenses, balance); | |
| // Update charts | |
| updateCharts(totalIncome, totalExpenses, expenseCategories); | |
| } | |
| // Update savings progress | |
| function updateSavingsProgress() { | |
| // Household | |
| const householdPercent = (financeData.savings.household.saved / financeData.savings.household.target * 100).toFixed(1); | |
| householdSavedElement.textContent = financeData.savings.household.saved.toLocaleString(); | |
| householdPercentElement.textContent = householdPercent; | |
| householdBarElement.style.width = `${Math.min(100, householdPercent)}%`; | |
| // Business | |
| const businessPercent = (financeData.savings.business.saved / financeData.savings.business.target * 100).toFixed(1); | |
| businessSavedElement.textContent = financeData.savings.business.saved.toLocaleString(); | |
| businessPercentElement.textContent = businessPercent; | |
| businessBarElement.style.width = `${Math.min(100, businessPercent)}%`; | |
| // Emergency | |
| const emergencyPercent = (financeData.savings.emergency.saved / financeData.savings.emergency.target * 100).toFixed(1); | |
| emergencySavedElement.textContent = financeData.savings.emergency.saved.toLocaleString(); | |
| emergencyPercentElement.textContent = emergencyPercent; | |
| emergencyBarElement.style.width = `${Math.min(100, emergencyPercent)}%`; | |
| } | |
| // Update financial status text | |
| function updateFinancialStatusText(totalIncome, totalExpenses, balance) { | |
| if (totalIncome === 0 && totalExpenses === 0) { | |
| financialStatusText.textContent = 'You haven\'t recorded any transactions yet. Start by adding your income and expenses to get a clear picture of your financial health.'; | |
| return; | |
| } | |
| let statusText = ''; | |
| if (totalIncome > totalExpenses) { | |
| const savingsRate = ((totalIncome - totalExpenses) / totalIncome * 100).toFixed(1); | |
| statusText = `You're doing well! Your income (${totalIncome.toLocaleString()} Ksh) exceeds your expenses (${totalExpenses.toLocaleString()} Ksh), giving you a savings rate of ${savingsRate}%. `; | |
| if (savingsRate > 20) { | |
| statusText += 'This is excellent! Consider investing some of your surplus to grow your wealth.'; | |
| } else if (savingsRate > 10) { | |
| statusText += 'This is good, but there might be room for improvement in your spending habits.'; | |
| } else { | |
| statusText += 'Try to increase your savings rate by reducing unnecessary expenses.'; | |
| } | |
| } else if (totalIncome < totalExpenses) { | |
| const deficit = totalExpenses - totalIncome; | |
| statusText = `Warning! Your expenses (${totalExpenses.toLocaleString()} Ksh) exceed your income (${totalIncome.toLocaleString()} Ksh), creating a deficit of ${deficit.toLocaleString()} Ksh. `; | |
| statusText += 'This is not sustainable. Please review your expenses and look for areas to cut back.'; | |
| } else { | |
| statusText = 'Your income and expenses are balanced. While you\'re not going into debt, you\'re also not saving anything. Consider finding ways to increase your income or reduce expenses to build savings.'; | |
| } | |
| financialStatusText.textContent = statusText; | |
| } | |
| // Update charts | |
| function updateCharts(totalIncome, totalExpenses, expenseCategories) { | |
| // Income vs Expenses bar chart | |
| const maxValue = Math.max(totalIncome, totalExpenses) || 1; | |
| incomeBar.style.height = `${(totalIncome / maxValue * 100)}%`; | |
| expenseBar.style.height = `${(totalExpenses / maxValue * 100)}%`; | |
| // Expense pie chart | |
| if (Object.keys(expenseCategories).length === 0) { | |
| expensePieChart.innerHTML = '<p class="text-gray-500">No expenses to display</p>'; | |
| return; | |
| } | |
| const sortedCategories = Object.entries(expenseCategories) | |
| .sort((a, b) => b[1] - a[1]); | |
| const colors = ['#BF1363', '#F39237', '#0E79B2', '#191923', '#FBFEF9']; | |
| expensePieChart.innerHTML = ''; | |
| // Create a simple pie chart using CSS | |
| const pieChart = document.createElement('div'); | |
| pieChart.className = 'relative w-48 h-48 rounded-full mx-auto'; | |
| let cumulativePercent = 0; | |
| sortedCategories.forEach(([category, amount], index) => { | |
| const percent = (amount / totalExpenses * 100); | |
| const slice = document.createElement('div'); | |
| slice.className = 'absolute top-0 left-0 w-full h-full'; | |
| slice.style.clipPath = `polygon(50% 50%, ${50 + 50 * Math.cos(cumulativePercent / 100 * 2 * Math.PI)}% ${50 + 50 * Math.sin(cumulativePercent / 100 * 2 * Math.PI)}%, ${50 + 50 * Math.cos((cumulativePercent + percent) / 100 * 2 * Math.PI)}% ${50 + 50 * Math.sin((cumulativePercent + percent) / 100 * 2 * Math.PI)}%)`; | |
| slice.style.backgroundColor = colors[index % colors.length]; | |
| pieChart.appendChild(slice); | |
| cumulativePercent += percent; | |
| }); | |
| expensePieChart.appendChild(pieChart); | |
| // Add legend | |
| const legend = document.createElement('div'); | |
| legend.className = 'flex flex-wrap justify-center mt-4 gap-2'; | |
| sortedCategories.forEach(([category, amount], index) => { | |
| const legendItem = document.createElement('div'); | |
| legendItem.className = 'flex items-center text-sm'; | |
| const colorBox = document.createElement('div'); | |
| colorBox.className = 'w-3 h-3 mr-1'; | |
| colorBox.style.backgroundColor = colors[index % colors.length]; | |
| const label = document.createElement('span'); | |
| label.textContent = `${capitalizeFirstLetter(category)} (${(amount / totalExpenses * 100).toFixed(1)}%)`; | |
| legendItem.appendChild(colorBox); | |
| legendItem.appendChild(label); | |
| legend.appendChild(legendItem); | |
| }); | |
| expensePieChart.appendChild(legend); | |
| // Update financial analysis text | |
| updateFinancialAnalysisText(totalIncome, totalExpenses, sortedCategories); | |
| } | |
| // Update financial analysis text | |
| function updateFinancialAnalysisText(totalIncome, totalExpenses, sortedCategories) { | |
| if (totalIncome === 0 && totalExpenses === 0) { | |
| financialAnalysisText.textContent = 'You haven\'t recorded enough transactions to generate a financial analysis. Start by adding your income and expenses to get valuable insights into your financial habits.'; | |
| return; | |
| } | |
| let analysisText = ''; | |
| // Net income analysis | |
| const netIncome = totalIncome - totalExpenses; | |
| if (netIncome > 0) { | |
| analysisText = `Your net income is positive at ${netIncome.toLocaleString()} Ksh, which means you're saving money each month. `; | |
| } else if (netIncome < 0) { | |
| analysisText = `Your net income is negative at ${Math.abs(netIncome).toLocaleString()} Ksh, which means you're spending more than you earn. `; | |
| } else { | |
| analysisText = 'Your income and expenses are exactly balanced this month. '; | |
| } | |
| // Top expense analysis | |
| if (sortedCategories.length > 0) { | |
| analysisText += 'Your largest expense category is ' + capitalizeFirstLetter(sortedCategories[0][0]) + | |
| `, accounting for ${(sortedCategories[0][1] / totalExpenses * 100).toFixed(1)}% of your total expenses. `; | |
| if (sortedCategories.length > 1) { | |
| analysisText += `Other significant expenses include ${capitalizeFirstLetter(sortedCategories[1][0])} `; | |
| if (sortedCategories.length > 2) { | |
| analysisText += `and ${capitalizeFirstLetter(sortedCategories[2][0])}. `; | |
| } else { | |
| analysisText += '. '; | |
| } | |
| } | |
| } | |
| // Savings rate analysis | |
| if (totalIncome > 0) { | |
| const savingsRate = ((totalIncome - totalExpenses) / totalIncome * 100).toFixed(1); | |
| analysisText += `Your savings rate is ${savingsRate}%. `; | |
| if (savingsRate >= 20) { | |
| analysisText += 'This is an excellent savings rate! Keep up the good work.'; | |
| } else if (savingsRate >= 10) { | |
| analysisText += 'This is a decent savings rate, but there might be room for improvement.'; | |
| } else { | |
| analysisText += 'Consider finding ways to increase your savings rate by either increasing income or reducing expenses.'; | |
| } | |
| } | |
| financialAnalysisText.textContent = analysisText; | |
| } | |
| // Add to savings | |
| function addToSavings(goal) { | |
| const inputElement = document.getElementById(`${goal}AddAmount`); | |
| const amount = parseFloat(inputElement.value); | |
| if (!amount || amount <= 0) { | |
| alert('Please enter a valid amount'); | |
| return; | |
| } | |
| // Check if balance allows this savings | |
| const totalIncome = financeData.transactions | |
| .filter(t => t.type === 'income') | |
| .reduce((sum, t) => sum + t.amount, 0); | |
| const totalExpenses = financeData.transactions | |
| .filter(t => t.type === 'expense') | |
| .reduce((sum, t) => sum + t.amount, 0); | |
| const totalSavings = financeData.savings.household.saved + | |
| financeData.savings.business.saved + | |
| financeData.savings.emergency.saved; | |
| const balance = totalIncome - totalExpenses - totalSavings; | |
| if (amount > balance) { | |
| alert(`You only have ${balance.toLocaleString()} Ksh available for savings.`); | |
| return; | |
| } | |
| // Add to savings | |
| financeData.savings[goal].saved += amount; | |
| // Record in history | |
| financeData.savings[goal].history.push({ | |
| date: new Date().toISOString(), | |
| amount, | |
| balance: financeData.savings[goal].saved | |
| }); | |
| saveData(); | |
| updateFinancialOverview(); | |
| renderSavingsHistory(); | |
| // Reset input | |
| inputElement.value = ''; | |
| } | |
| // Render savings history | |
| function renderSavingsHistory() { | |
| const history = []; | |
| // Combine all savings history | |
| Object.entries(financeData.savings).forEach(([goal, data]) => { | |
| data.history.forEach(entry => { | |
| history.push({ | |
| ...entry, | |
| goal: capitalizeFirstLetter(goal) | |
| }); | |
| }); | |
| }); | |
| // Sort by date | |
| history.sort((a, b) => new Date(b.date) - new Date(a.date)); | |
| if (history.length === 0) { | |
| savingsHistory.innerHTML = ` | |
| <tr> | |
| <td colspan="4" class="p-4 text-center text-gray-500">No savings recorded yet</td> | |
| </tr> | |
| `; | |
| return; | |
| } | |
| savingsHistory.innerHTML = history.map(entry => ` | |
| <tr class="border-b"> | |
| <td class="p-2">${formatDate(entry.date)}</td> | |
| <td class="p-2">${entry.goal}</td> | |
| <td class="p-2 text-right">+${entry.amount.toLocaleString()} Ksh</td> | |
| <td class="p-2 text-right">${entry.balance.toLocaleString()} Ksh</td> | |
| </tr> | |
| `).join(''); | |
| } | |
| // Generate monthly report | |
| generateReportBtn.addEventListener('click', () => { | |
| const month = parseInt(reportMonthSelect.value); | |
| const year = parseInt(reportYearSelect.value); | |
| // Filter transactions for the selected month/year | |
| const monthTransactions = financeData.transactions.filter(t => { | |
| const date = new Date(t.date); | |
| return date.getMonth() + 1 === month && date.getFullYear() === year; | |
| }); | |
| const monthIncome = monthTransactions | |
| .filter(t => t.type === 'income') | |
| .reduce((sum, t) => sum + t.amount, 0); | |
| const monthExpenses = monthTransactions | |
| .filter(t => t.type === 'expense') | |
| .reduce((sum, t) => sum + t.amount, 0); | |
| const monthSavings = monthIncome - monthExpenses; | |
| // Get top 3 expense categories | |
| const expenseCategories = {}; | |
| monthTransactions | |
| .filter(t => t.type === 'expense') | |
| .forEach(t => { | |
| expenseCategories[t.category] = (expenseCategories[t.category] || 0) + t.amount; | |
| }); | |
| const topExpenses = Object.entries(expenseCategories) | |
| .sort((a, b) => b[1] - a[1]) | |
| .slice(0, 3); | |
| // Update report UI | |
| reportTitle.textContent = `${new Date(year, month - 1).toLocaleString('default', { month: 'long' })} ${year}`; | |
| reportIncome.textContent = `${monthIncome.toLocaleString()} Ksh`; | |
| reportExpenses.textContent = `${monthExpenses.toLocaleString()} Ksh`; | |
| reportSavings.textContent = `${monthSavings.toLocaleString()} Ksh`; | |
| // Update top expenses | |
| if (topExpenses.length === 0) { | |
| reportTopExpenses.innerHTML = '<p class="text-gray-600">No expense data available</p>'; | |
| } else { | |
| reportTopExpenses.innerHTML = topExpenses.map(([category, amount]) => ` | |
| <div class="flex justify-between mb-1"> | |
| <span class="capitalize">${category}</span> | |
| <span class="font-medium">${amount.toLocaleString()} Ksh (${(amount / monthExpenses * 100).toFixed(1)}%)</span> | |
| </div> | |
| `).join(''); | |
| } | |
| // Generate summary text | |
| let summary = `In ${new Date(year, month - 1).toLocaleString('default', { month: 'long' })}, you earned ${monthIncome.toLocaleString()} Ksh and spent ${monthExpenses.toLocaleString()} Ksh. `; | |
| if (monthIncome > monthExpenses) { | |
| summary += `This resulted in a net savings of ${monthSavings.toLocaleString()} Ksh. `; | |
| if (topExpenses.length > 0) { | |
| summary += `Your largest expense was ${topExpenses[0][0]}, accounting for ${(topExpenses[0][1] / monthExpenses * 100).toFixed(1)}% of your total expenses.`; | |
| } | |
| } else if (monthIncome < monthExpenses) { | |
| summary += `This resulted in a deficit of ${Math.abs(monthSavings).toLocaleString()} Ksh. `; | |
| if (topExpenses.length > 0) { | |
| summary += `Consider reviewing your spending on ${topExpenses[0][0]}, which accounted for ${(topExpenses[0][1] / monthExpenses * 100).toFixed(1)}% of your expenses.`; | |
| } | |
| } else { | |
| summary += 'Your income and expenses were exactly balanced this month.'; | |
| } | |
| reportSummary.textContent = summary; | |
| // Show report | |
| monthlyReport.classList.remove('hidden'); | |
| }); | |
| // To-Do List functionality | |
| addTaskBtn.addEventListener('click', () => { | |
| const title = taskTitleInput.value.trim(); | |
| const priority = taskPrioritySelect.value; | |
| const deadline = taskDeadlineInput.value; | |
| const description = taskDescriptionInput.value.trim(); | |
| if (!title) { | |
| alert('Please enter a task title'); | |
| return; | |
| } | |
| if (!deadline) { | |
| alert('Please select a deadline'); | |
| return; | |
| } | |
| const task = { | |
| id: Date.now(), | |
| title, | |
| priority, | |
| deadline, | |
| description, | |
| completed: false, | |
| createdAt: new Date().toISOString() | |
| }; | |
| financeData.todoList.push(task); | |
| saveData(); | |
| renderTasks(); | |
| // Reset form | |
| taskTitleInput.value = ''; | |
| taskPrioritySelect.value = 'medium'; | |
| taskDeadlineInput.value = ''; | |
| taskDescriptionInput.value = ''; | |
| }); | |
| // Filter tasks | |
| taskFilterStatus.addEventListener('change', renderTasks); | |
| taskFilterPriority.addEventListener('change', renderTasks); | |
| // Render tasks | |
| function renderTasks() { | |
| const filterStatus = taskFilterStatus.value; | |
| const filterPriority = taskFilterPriority.value; | |
| const filteredTasks = financeData.todoList.filter(task => { | |
| const matchesStatus = filterStatus === 'all' || | |
| (filterStatus === 'pending' && !task.completed) || | |
| (filterStatus === 'completed' && task.completed); | |
| const matchesPriority = filterPriority === 'all' || task.priority === filterPriority; | |
| return matchesStatus && matchesPriority; | |
| }); | |
| if (filteredTasks.length === 0) { | |
| taskList.innerHTML = '<li class="p-4 text-center text-gray-500">No tasks found</li>'; | |
| return; | |
| } | |
| // Sort by deadline (soonest first), then priority (high first), then creation date (newest first) | |
| filteredTasks.sort((a, b) => { | |
| // Completed tasks go to bottom | |
| if (a.completed && !b.completed) return 1; | |
| if (!a.completed && b.completed) return -1; | |
| // Sort by deadline | |
| const deadlineA = new Date(a.deadline); | |
| const deadlineB = new Date(b.deadline); | |
| if (deadlineA < deadlineB) return -1; | |
| if (deadlineA > deadlineB) return 1; | |
| // Sort by priority | |
| const priorityOrder = { high: 3, medium: 2, low: 1 }; | |
| if (priorityOrder[a.priority] > priorityOrder[b.priority]) return -1; | |
| if (priorityOrder[a.priority] < priorityOrder[b.priority]) return 1; | |
| // Sort by creation date | |
| return new Date(b.createdAt) - new Date(a.createdAt); | |
| }); | |
| taskList.innerHTML = filteredTasks.map(task => ` | |
| <li class="border-b p-4 ${task.completed ? 'bg-gray-50' : ''}"> | |
| <div class="flex items-start justify-between"> | |
| <div class="flex items-start"> | |
| <input type="checkbox" ${task.completed ? 'checked' : ''} | |
| class="mt-1 mr-2" | |
| onchange="toggleTaskCompletion(${task.id}, this.checked)"> | |
| <div> | |
| <h4 class="font-medium ${task.completed ? 'line-through text-gray-500' : ''}">${task.title}</h4> | |
| ${task.description ? `<p class="text-sm text-gray-600 mt-1">${task.description}</p>` : ''} | |
| <div class="flex items-center mt-2 text-sm"> | |
| <span class="inline-block w-3 h-3 rounded-full mr-1 | |
| ${task.priority === 'high' ? 'bg-red-500' : | |
| task.priority === 'medium' ? 'bg-yellow-500' : 'bg-green-500'}"></span> | |
| <span class="mr-4 capitalize">${task.priority} priority</span> | |
| <span class="text-gray-500"><i class="far fa-calendar-alt mr-1"></i> Due: ${formatDate(task.deadline)}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <button class="text-red-500 hover:text-red-700" onclick="deleteTask(${task.id})"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| </li> | |
| `).join(''); | |
| } | |
| // Toggle task completion | |
| function toggleTaskCompletion(id, completed) { | |
| const task = financeData.todoList.find(t => t.id === id); | |
| if (task) { | |
| task.completed = completed; | |
| saveData(); | |
| renderTasks(); | |
| } | |
| } | |
| // Delete task | |
| function deleteTask(id) { | |
| if (confirm('Are you sure you want to delete this task?')) { | |
| financeData.todoList = financeData.todoList.filter(t => t.id !== id); | |
| saveData(); | |
| renderTasks(); | |
| } | |
| } | |
| // Calendar functionality | |
| let currentCalendarDate = new Date(); | |
| // Initialize calendar buttons | |
| prevMonthBtn.addEventListener('click', () => { | |
| currentCalendarDate.setMonth(currentCalendarDate.getMonth() - 1); | |
| renderCalendar(); | |
| }); | |
| nextMonthBtn.addEventListener('click', () => { | |
| currentCalendarDate.setMonth(currentCalendarDate.getMonth() + 1); | |
| renderCalendar(); | |
| }); | |
| todayBtn.addEventListener('click', () => { | |
| currentCalendarDate = new Date(); | |
| renderCalendar(); | |
| }); | |
| // Render calendar | |
| function renderCalendar() { | |
| const year = currentCalendarDate.getFullYear(); | |
| const month = currentCalendarDate.getMonth(); | |
| // Set month/year title | |
| calendarMonthYear.textContent = new Date(year, month).toLocaleString('default', { month: 'long', year: 'numeric' }); | |
| // Get first day of month and last day of month | |
| const firstDay = new Date(year, month, 1); | |
| const lastDay = new Date(year, month + 1, 0); | |
| // Get days in month | |
| const daysInMonth = lastDay.getDate(); | |
| // Get starting day of week (0 = Sunday, 6 = Saturday) | |
| const startingDay = firstDay.getDay(); | |
| // Clear previous calendar | |
| calendarDays.innerHTML = ''; | |
| // Add empty cells for days before the first day of the month | |
| for (let i = 0; i < startingDay; i++) { | |
| const emptyCell = document.createElement('div'); | |
| emptyCell.className = 'p-2 border'; | |
| calendarDays.appendChild(emptyCell); | |
| } | |
| // Add cells for each day of the month | |
| for (let day = 1; day <= daysInMonth; day++) { | |
| const date = new Date(year, month, day); | |
| const dateString = date.toISOString().split('T')[0]; | |
| const dayCell = document.createElement('div'); | |
| dayCell.className = 'calendar-day p-2 border h-24 overflow-y-auto'; | |
| // Highlight today | |
| const today = new Date(); | |
| if (date.getDate() === today.getDate() && | |
| date.getMonth() === today.getMonth() && | |
| date.getFullYear() === today.getFullYear()) { | |
| dayCell.classList.add('today'); | |
| } | |
| // Check if day has events | |
| const hasEvents = financeData.calendarEvents[dateString] && financeData.calendarEvents[dateString].length > 0; | |
| if (hasEvents) { | |
| dayCell.classList.add('has-event'); | |
| } | |
| dayCell.innerHTML = ` | |
| <div class="font-medium">${day}</div> | |
| ${hasEvents ? `<div class="text-xs mt-1 text-white bg-accent2 rounded-full px-1 inline-block">${financeData.calendarEvents[dateString].length} event(s)</div>` : ''} | |
| `; | |
| dayCell.addEventListener('click', () => showDayEvents(date)); | |
| calendarDays.appendChild(dayCell); | |
| } | |
| // Set selected day to today if not already set | |
| const today = new Date(); | |
| if (!document.querySelector('.calendar-day.selected')) { | |
| showDayEvents(today); | |
| } | |
| } | |
| // Show events for a specific day | |
| function showDayEvents(date) { | |
| const dateString = date.toISOString().split('T')[0]; | |
| selectedDayTitle.textContent = date.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); | |
| // Clear previous selection | |
| document.querySelectorAll('.calendar-day').forEach(day => { | |
| day.classList.remove('selected', 'bg-accent1', 'text-primary'); | |
| }); | |
| // Highlight selected day | |
| const dayElements = document.querySelectorAll('.calendar-day'); | |
| const dayIndex = Array.from(dayElements).findIndex(day => { | |
| return day.textContent.includes(date.getDate().toString()) && | |
| !day.textContent.includes((date.getDate() + 1).toString()); | |
| }); | |
| if (dayIndex !== -1) { | |
| dayElements[dayIndex].classList.add('selected', 'bg-accent1', 'text-primary'); | |
| } | |
| // Show events for this day | |
| const events = financeData.calendarEvents[dateString] || []; | |
| if (events.length === 0) { | |
| dayEventsList.innerHTML = '<li class="text-gray-500">No events scheduled</li>'; | |
| } else { | |
| dayEventsList.innerHTML = events.map(event => ` | |
| <li class="border-b py-2"> | |
| <div class="flex justify-between items-start"> | |
| <div> | |
| <h4 class="font-medium">${event.title}</h4> | |
| <p class="text-sm text-gray-600">${event.time}</p> | |
| ${event.description ? `<p class="text-sm mt-1">${event.description}</p>` : ''} | |
| </div> | |
| <button class="text-red-500 hover:text-red-700" onclick="deleteEvent('${dateString}', ${event.id})"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| </li> | |
| `).join(''); | |
| } | |
| // Set current date for adding new events | |
| eventTitleInput.dataset.date = dateString; | |
| } | |
| // Add event | |
| addEventBtn.addEventListener('click', () => { | |
| const title = eventTitleInput.value.trim(); | |
| const time = eventTimeInput.value; | |
| const description = eventDescriptionInput.value.trim(); | |
| const dateString = eventTitleInput.dataset.date; | |
| if (!title) { | |
| alert('Please enter an event title'); | |
| return; | |
| } | |
| if (!time) { | |
| alert('Please select a time'); | |
| return; | |
| } | |
| const event = { | |
| id: Date.now(), | |
| title, | |
| time, | |
| description | |
| }; | |
| // Initialize events array for this date if it doesn't exist | |
| if (!financeData.calendarEvents[dateString]) { | |
| financeData.calendarEvents[dateString] = []; | |
| } | |
| financeData.calendarEvents[dateString].push(event); | |
| saveData(); | |
| // Refresh calendar and events list | |
| renderCalendar(); | |
| showDayEvents(new Date(dateString)); | |
| // Reset form | |
| eventTitleInput.value = ''; | |
| eventTimeInput.value = ''; | |
| eventDescriptionInput.value = ''; | |
| }); | |
| // Delete event | |
| function deleteEvent(dateString, id) { | |
| if (confirm('Are you sure you want to delete this event?')) { | |
| financeData.calendarEvents[dateString] = financeData.calendarEvents[dateString].filter(e => e.id !== id); | |
| // Remove date key if no events left | |
| if (financeData.calendarEvents[dateString].length === 0) { | |
| delete financeData.calendarEvents[dateString]; | |
| } | |
| saveData(); | |
| // Refresh calendar and events list | |
| renderCalendar(); | |
| showDayEvents(new Date(dateString)); | |
| } | |
| } | |
| // Helper functions | |
| function formatDate(dateString) { | |
| const date = new Date(dateString); | |
| return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); | |
| } | |
| function capitalizeFirstLetter(string) { | |
| return string.charAt(0).toUpperCase() + string.slice(1); | |
| } | |
| // Save data to localStorage | |
| function saveData() { | |
| localStorage.setItem('benMburuFinanceData', JSON.stringify(financeData)); | |
| } | |
| // Load data from localStorage | |
| function loadData() { | |
| const savedData = localStorage.getItem('benMburuFinanceData'); | |
| if (savedData) { | |
| financeData = JSON.parse(savedData); | |
| // Convert date strings to Date objects for transactions | |
| financeData.transactions.forEach(t => { | |
| t.date = new Date(t.date).toISOString().split('T')[0]; | |
| }); | |
| // Convert date strings to Date objects for tasks | |
| financeData.todoList.forEach(t => { | |
| t.deadline = new Date(t.deadline).toISOString().split('T')[0]; | |
| }); | |
| } | |
| } | |
| // Initialize app | |
| function init() { | |
| loadData(); | |
| // Set current date for transaction | |
| transactionDateInput.valueAsDate = new Date(); | |
| // Set current date for task deadline | |
| taskDeadlineInput.valueAsDate = new Date(); | |
| // Set current time for event | |
| const | |
| <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=Khatvathiren/track" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |