File size: 20,316 Bytes
d051f74
 
 
1c3a6ac
904e2dc
83a8c71
204d146
83a8c71
448d7d8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1c3a6ac
 
c08a432
1c3a6ac
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8a85af4
 
1c3a6ac
 
 
 
 
 
448d7d8
 
204d146
448d7d8
 
204d146
448d7d8
 
 
 
f97528d
204d146
448d7d8
 
 
 
 
 
 
 
 
 
 
 
204d146
448d7d8
 
 
 
 
204d146
 
 
c08a432
448d7d8
 
c08a432
 
 
 
 
 
 
204d146
 
 
 
c08a432
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33c20cc
 
 
 
 
 
 
 
 
 
 
 
c08a432
 
 
 
 
 
 
 
 
 
 
 
204d146
 
 
 
 
 
1c3a6ac
204d146
 
 
 
f25ab1d
448d7d8
 
 
1c3a6ac
204d146
 
1c3a6ac
 
 
 
 
 
 
 
 
 
 
 
 
204d146
 
 
 
 
1c3a6ac
204d146
 
 
 
f25ab1d
448d7d8
 
 
1c3a6ac
204d146
 
1c3a6ac
 
 
 
 
 
 
 
 
 
 
 
 
bf07a4c
204d146
 
886e2b3
 
730bb18
886e2b3
 
448d7d8
 
6d9f310
7792641
6d9f310
c75e49a
6d9f310
f25ab1d
 
6d9f310
 
 
bf07a4c
 
 
886e2b3
7792641
448d7d8
 
204d146
34a7169
925987e
204d146
448d7d8
925987e
 
 
 
37a53fe
925987e
e09300a
a165e43
 
 
 
 
 
e09300a
1c3a6ac
e09300a
925987e
a165e43
1c3a6ac
a165e43
 
 
 
 
 
 
 
e09300a
 
 
 
b8c4511
e09300a
 
 
 
 
 
 
 
 
 
 
 
 
 
b8c4511
e09300a
a165e43
e09300a
 
 
 
b8c4511
e09300a
 
 
17dfb56
e09300a
 
 
 
 
 
 
 
649b70f
e09300a
479b884
e09300a
 
 
649b70f
e09300a
 
 
 
649b70f
e09300a
c91a76e
c08a432
e09300a
649b70f
e09300a
 
 
f25ab1d
e09300a
 
649b70f
c91a76e
649b70f
e09300a
 
649b70f
e09300a
 
 
17dfb56
479b884
bf26452
a165e43
649b70f
b8c4511
 
 
925987e
 
448d7d8
925987e
1c3a6ac
 
 
 
 
 
925987e
 
 
 
 
 
 
 
1c3a6ac
204d146
 
 
 
1c3a6ac
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
448d7d8
1c3a6ac
448d7d8
 
 
1c3a6ac
9ee4679
448d7d8
1c3a6ac
448d7d8
1c3a6ac
448d7d8
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
import streamlit as st
import pandas as pd
import matplotlib.pyplot as plt
from datetime import date, datetime
import numpy as np
import os
import json

# Local storage file for user credentials
CREDENTIALS_FILE = "user_credentials.json"

# Initialize credentials file if it doesn't exist
def initialize_credentials_file():
    if not os.path.exists(CREDENTIALS_FILE):
        with open(CREDENTIALS_FILE, "w") as f:
            json.dump({}, f)

# Save user credentials
def save_user_credentials(username, password):
    with open(CREDENTIALS_FILE, "r") as f:
        credentials = json.load(f)
    credentials[username] = password
    with open(CREDENTIALS_FILE, "w") as f:
        json.dump(credentials, f)

# Validate user credentials
def validate_credentials(username, password):
    with open(CREDENTIALS_FILE, "r") as f:
        credentials = json.load(f)
    return credentials.get(username) == password

# Check if username exists
def username_exists(username):
    with open(CREDENTIALS_FILE, "r") as f:
        credentials = json.load(f)
    return username in credentials

# Initialize credentials file
initialize_credentials_file()

# Session state for user management
if "is_authenticated" not in st.session_state:
    st.session_state["is_authenticated"] = False
if "current_user" not in st.session_state:
    st.session_state["current_user"] = None

# Initialize session state
if "income_data" not in st.session_state:
    st.session_state["income_data"] = pd.DataFrame(columns=["Date", "Category", "Amount"])

if "expense_data" not in st.session_state:
    st.session_state["expense_data"] = pd.DataFrame(columns=["Date", "Category", "Amount"])

if "custom_income_sources" not in st.session_state:
    st.session_state["custom_income_sources"] = []

if "custom_expense_categories" not in st.session_state:
    st.session_state["custom_expense_categories"] = []

if "saving_goals" not in st.session_state:
    st.session_state["saving_goals"] = pd.DataFrame(columns=["Goal", "Target Amount", "Current Savings", "Deadline", "Allocated Savings"])

# Helper functions
def format_date(date_obj):
    return date_obj.strftime("%d-%m-%y")

def update_transaction_log():
    st.session_state["transaction_log"] = pd.concat([ 
        st.session_state["income_data"].assign(Type="Income"),
        st.session_state["expense_data"].assign(Type="Expense")
        #st.session_state["saving_transaction_log"].assign(Type="Saving")
    ]).reset_index(drop=True)
    st.session_state["transaction_log"].index += 1

if "transaction_log" not in st.session_state:
    update_transaction_log()

# Login and Sign-Up Pages
def login_page():
    st.title("Login")
    username = st.text_input("Username")
    password = st.text_input("Password", type="password")
    if st.button("Login"):
        if validate_credentials(username, password):
            st.session_state["is_authenticated"] = True
            st.session_state["current_user"] = username
            st.success("Login successful!")
            st.rerun()
        else:
            st.error("Invalid username or password")

def signup_page():
    st.title("Sign Up")
    username = st.text_input("Choose a Username")
    password = st.text_input("Choose a Password", type="password")
    if st.button("Sign Up"):
        if username_exists(username):
            st.error("Username already exists. Please choose a different username.")
        else:
            save_user_credentials(username, password)
            st.success("Sign up successful! You can now log in.")

# Main app after login
def main_app():
    # Sidebar for navigation
    st.sidebar.title("Navigation")
    tabs = st.sidebar.radio("", ["Home", "Record Income", "Record Expense", "Transaction Log", "Savings", "Budget Recommendations", "Logout"])

    # Home Tab
    if tabs == "Home":
        # Total income, expense, and net savings
        total_income = st.session_state['income_data']['Amount'].sum()
        total_expense = st.session_state['expense_data']['Amount'].sum()
        allocated_savings = st.session_state["saving_goals"]["Allocated Savings"].sum()
        net_savings = total_income - total_expense - allocated_savings
        
        # Cash in hand (remaining income after subtracting expenses and allocated savings)
        cash_in_hand = total_income - total_expense - allocated_savings
    
        # Show metrics
        col1, col2, col3 = st.columns(3)
        col1.metric("Total Income", f"{total_income:,}")
        col2.metric("Total Expense", f"{total_expense:,}")
        col3.metric("Net Savings", f"{net_savings:,}")
        
        # Display Cash in Hand (remaining after expenses and savings)
        col4, col5 = st.columns(2)
        col4.metric("Cash in Hand", f"{cash_in_hand:,}")
    
        # Find the nearest goal
        if not st.session_state["saving_goals"].empty:
            closest_goal = st.session_state["saving_goals"].iloc[0]  # Default to first goal
            closest_goal_amount = closest_goal["Target Amount"] - closest_goal["Current Savings"]
            closest_goal_name = closest_goal["Goal"]
            closest_goal_deadline = closest_goal["Deadline"]
            
            # Loop through goals and find the closest one based on the deadline
            for _, goal in st.session_state["saving_goals"].iterrows():
                remaining_amount = goal["Target Amount"] - goal["Current Savings"]
                if goal["Deadline"] < closest_goal_deadline and remaining_amount > 0:
                    closest_goal = goal
                    closest_goal_name = goal["Goal"]
                    closest_goal_deadline = goal["Deadline"]
                    closest_goal_amount = remaining_amount
    
            # Display label for the nearest goal
            st.write(f"Amount needed to reach goal '{closest_goal_name}': {closest_goal_amount:,} (Deadline: {closest_goal_deadline})")

        if total_income > 0 and total_expense > 0:
            # Pie chart for income breakdown
            labels = ['Expense', 'Savings', 'Unallocated Income']
            sizes = [total_expense, allocated_savings, cash_in_hand]
            sizes = [0 if np.isnan(value) else value for value in sizes]
            colors = ['#ff9999','#66b3ff','#99ff99']
            
            fig, ax = plt.subplots()
            ax.pie(sizes, labels=labels, autopct='%1.1f%%', startangle=90, colors=colors)
            ax.axis('equal')  # Equal aspect ratio ensures that pie chart is drawn as a circle.
            st.pyplot(fig)
    
        # Progress bar for each goal
        for idx, goal in st.session_state["saving_goals"].iterrows():
            goal_name = goal["Goal"]
            target_amount = goal["Target Amount"]
            current_savings = goal["Current Savings"]
            
            progress = current_savings / target_amount * 100  # Progress as percentage
            
            st.subheader(f"Progress for Goal: {goal_name}")
            st.progress(progress)
            st.write(f"Target Amount: {target_amount}, Current Savings: {current_savings}")

    # Record Income Tab
    elif tabs == "Record Income":
        st.header("Record Income")
        with st.form("income_form"):
            income_date = st.date_input("Date", value=date.today())
            income_source = st.selectbox("Source", ["Salary", "Freelance", "Investments"] + st.session_state["custom_income_sources"])
            income_amount = st.number_input("Amount", min_value=0, step=1)
            submit_income = st.form_submit_button("Add Income")

        if submit_income:
            new_entry = {"Date": income_date, "Category": "Income from "+income_source, "Amount": income_amount}
            st.session_state["income_data"] = pd.concat(
                [st.session_state["income_data"], pd.DataFrame([new_entry])], ignore_index=True
            )
            update_transaction_log()
            st.success("Income added successfully!")

        if not st.session_state["income_data"].empty:
            fig, ax = plt.subplots()
            st.session_state['income_data']['Date'] = pd.to_datetime(st.session_state['income_data']['Date'])
            sorted_data = st.session_state["income_data"].sort_values("Date")
            ax.bar(sorted_data["Date"], sorted_data["Amount"], label='Income', width=0.4)
            ax.plot(sorted_data["Date"], sorted_data["Amount"].cumsum(), label='Cumulative Income', marker='o', color='green')
            ax.set_title("Income Over Time")
            ax.set_xlabel("Date")
            ax.set_ylabel("Amount")
            plt.xticks(rotation=45)
            ax.legend()
            st.pyplot(fig)

    # Record Expense Tab
    elif tabs == "Record Expense":
        st.header("Record Expense")
        with st.form("expense_form"):
            expense_date = st.date_input("Date", value=date.today())
            expense_category = st.selectbox("Category", ["Food", "Travel", "Utilities"] + st.session_state["custom_expense_categories"])
            expense_amount = st.number_input("Amount", min_value=0, step=1)
            submit_expense = st.form_submit_button("Add Expense")

        if submit_expense:
            new_entry = {"Date": expense_date, "Category": "Expense for " + expense_category, "Amount": expense_amount}
            st.session_state["expense_data"] = pd.concat(
                [st.session_state["expense_data"], pd.DataFrame([new_entry])], ignore_index=True
            )
            update_transaction_log()
            st.success("Expense added successfully!")

        if not st.session_state["expense_data"].empty:
            fig, ax = plt.subplots()
            st.session_state['expense_data']['Date'] = pd.to_datetime(st.session_state['expense_data']['Date'])
            sorted_data = st.session_state["expense_data"].sort_values("Date")
            ax.bar(sorted_data["Date"], sorted_data["Amount"], label='Expense', width=0.4)
            ax.plot(sorted_data["Date"], sorted_data["Amount"].cumsum(), label='Cumulative Expense', marker='o', color='red')
            ax.set_title("Expenses Over Time")
            ax.set_xlabel("Date")
            ax.set_ylabel("Amount")
            plt.xticks(rotation=45)
            ax.legend()
            st.pyplot(fig)

    # Transaction Log Tab
    elif tabs == "Transaction Log":
        st.header("Transaction Log")
        
        # Check if income, expense, or saving transactions exist
        if "income_data" in st.session_state and "expense_data" in st.session_state:
            
            # Concatenate income, expense, and saving transactions
            transaction_log = pd.concat([
                st.session_state["income_data"].assign(Type="Income"),
                st.session_state["expense_data"].assign(Type="Expense")
            ]).sort_values(by="Date")

            if "saving_transaction_log" in st.session_state:
                transaction_log = pd.concat([
                    st.session_state["income_data"].assign(Type="Income"),
                    st.session_state["expense_data"].assign(Type="Expense"),
                    st.session_state["saving_transaction_log"].assign(Type="Saving")  # Add saving transactions
                ]).sort_values(by="Date")
                
            # Ensure 'Date' column is of type datetime64
            transaction_log["Date"] = pd.to_datetime(transaction_log["Date"])
            
            # Display the combined transaction log
            st.dataframe(transaction_log)
        else:
            st.warning("No transactions recorded yet.")


    # Set Saving Goals Tab
    elif tabs == "Savings":
        st.header("Savings Goals")
        
        # Fetch net savings from Home tab
        total_income = st.session_state['income_data']['Amount'].sum()
        total_expense = st.session_state['expense_data']['Amount'].sum()
        net_savings = 0
        max_savings = total_income - total_expense - net_savings
        
        # Initialize session state for buttons if not already done
        if "show_set_goal_form" not in st.session_state:
            st.session_state["show_set_goal_form"] = False
        if "show_allocate_savings_form" not in st.session_state:
            st.session_state["show_allocate_savings_form"] = False
        
        # Buttons to toggle between setting goal and allocating savings
        set_goal_button = st.button("Set Saving Goal")
        allocate_savings_button = st.button("Allocate Saving to Goal")
        
        # Toggle the display state for each form
        if set_goal_button:
            st.session_state["show_set_goal_form"] = True
            st.session_state["show_allocate_savings_form"] = False  # Hide other form
        if allocate_savings_button:
            st.session_state["show_allocate_savings_form"] = True
            st.session_state["show_set_goal_form"] = False  # Hide other form
        
        # Set Saving Goal Form
        if st.session_state["show_set_goal_form"]:
            st.subheader("Set Saving Goal")
            goal_name = st.text_input("Goal Name")
            target_amount = st.number_input("Target Amount", min_value=0, step=100)
            goal_deadline = st.date_input("Deadline", value=date.today())
            
            # Button to create a new goal
            if st.button("Create Goal"):
                if goal_name and target_amount > 0:
                    new_goal = {"Goal": goal_name, 
                                "Target Amount": target_amount, 
                                "Current Savings": net_savings, 
                                "Deadline": goal_deadline, 
                                "Allocated Savings": 0}
                    st.session_state["saving_goals"] = pd.concat(
                        [st.session_state["saving_goals"], pd.DataFrame([new_goal])], ignore_index=True
                    )
                    st.success("Goal set successfully!")
                else:
                    st.error("Please provide valid goal name and target amount.")
        
        # Allocate Savings Form
        if st.session_state["show_allocate_savings_form"]:
            st.subheader("Allocate Savings to Goals")
            if not st.session_state["saving_goals"].empty:
                # Add a dropdown to select a goal
                goal_name_selected = st.selectbox("Select Saving Goal", st.session_state["saving_goals"]["Goal"].values)
                
                if goal_name_selected:
                    # Fetch the selected goal details
                    selected_goal = st.session_state["saving_goals"][st.session_state["saving_goals"]["Goal"] == goal_name_selected].iloc[0]
                    
                    # Show the current progress and target amount of the selected goal
                    st.write(f"**{goal_name_selected}**")
                    st.write(f"Target Amount: {selected_goal['Target Amount']}, Current Savings: {selected_goal['Current Savings']}, Deadline: {selected_goal['Deadline']}")
            
                    # Input field to allocate savings
                    allocated_savings = st.number_input(f"Enter Amount to Allocate for {goal_name_selected}", 
                                                       min_value=0, max_value=max_savings, step=100, key=f"allocate_{goal_name_selected}")
            
                    # Button to save the allocation for a goal
                    save_allocation_button = st.button(f"Save Allocation for {goal_name_selected}")
                    
                    if save_allocation_button and allocated_savings > 0:
                        # Find the index of the selected goal and update the savings
                        idx = st.session_state["saving_goals"][st.session_state["saving_goals"]["Goal"] == goal_name_selected].index[0]
                    
                        # Update the saving goal
                        st.session_state["saving_goals"].at[idx, "Allocated Savings"] += allocated_savings
                        max_savings -= allocated_savings  # Adjust max savings
                        st.session_state["saving_goals"].at[idx, "Current Savings"] += allocated_savings  # Update current savings
                        
                        # Ensure the transaction log exists in session_state
                        if "saving_transaction_log" not in st.session_state:
                            st.session_state["saving_transaction_log"] = pd.DataFrame(columns=["Date", "Amount", "Category"])
                    
                        # Append the transaction entry for the saving allocation
                        transaction_entry = {
                            "Date": pd.to_datetime("today"),  # Ensuring Date is a datetime object
                            "Amount": allocated_savings,
                            "Category": "Savings for " + goal_name_selected
                        }
                        
                        # Append the new transaction entry to the transaction log
                        st.session_state["saving_transaction_log"] = st.session_state["saving_transaction_log"].append(transaction_entry, ignore_index=True)
                        
                        # Success message
                        st.success(f"Allocated {allocated_savings} to goal '{goal_name_selected}'")
                        
                    elif save_allocation_button:
                        st.error("Please allocate a valid amount greater than 0.")






        else:
            st.write("No saving goals set yet.")

        
        # Display current goals and their progress
        if not st.session_state["saving_goals"].empty:
            st.subheader("Current Goals and Allocated Savings")
            for idx, goal in st.session_state["saving_goals"].iterrows():
                progress = (goal["Current Savings"] / goal["Target Amount"]) if goal["Target Amount"] > 0 else 0
                st.write(f"**{goal['Goal']}**")
                st.write(f"Target Amount: {goal['Target Amount']}, Current Savings: {goal['Current Savings']}, Deadline: {goal['Deadline']}")
                st.progress(progress)  # progress as a fraction (0.0 to 1.0)
                st.write(f"Progress: {progress * 100:.2f}%")  # Display as a percentage
    
            # Option to delete a goal
            delete_goal_idx = st.selectbox("Select Goal to Delete", options=[""] + list(st.session_state["saving_goals"]["Goal"]))
            if delete_goal_idx:
                st.session_state["saving_goals"] = st.session_state["saving_goals"][st.session_state["saving_goals"]["Goal"] != delete_goal_idx]
                st.success(f"Goal '{delete_goal_idx}' deleted successfully.")
        else:
            st.write("No savings goals set yet.")


    # Budget Recommendations Tab
    elif tabs == "Budget Recommendations":
        st.header("Budget Recommendations")
        if st.session_state["income_data"].empty or st.session_state["expense_data"].empty:
            st.warning("Please record your income and expenses before getting budget recommendations.")
        else:
            total_income = st.session_state["income_data"]["Amount"].sum()
            total_expense = st.session_state["expense_data"]["Amount"].sum()

            # Display budget allocation advice
            budget_allocation = {
                "Housing": total_income * 0.30,
                "Transportation": total_income * 0.15,
                "Food": total_income * 0.10,
                "Savings": total_income * 0.20,
                "Entertainment": total_income * 0.05,
                "Health": total_income * 0.10,
                "Miscellaneous": total_income * 0.10
            }
            st.write("Budget Recommendations based on your income:")
            for category, amount in budget_allocation.items():
                st.write(f"{category}: {amount:,.2f}")

    # Logout Tab
    elif tabs == "Logout":
        st.session_state["is_authenticated"] = False
        st.session_state["current_user"] = None
        st.success("Logged out successfully!")
        st.rerun()

# Streamlit App Logic
if not st.session_state["is_authenticated"]:
    page = st.selectbox("Choose a page", ["Login", "Sign Up"])
    if page == "Login":
        login_page()
    elif page == "Sign Up":
        signup_page()
else:
    main_app()