import streamlit as st import pandas as pd from datetime import date st.set_page_config(page_title="Family Expense Manager", page_icon="๐Ÿ’ฐ", layout="wide") # ---------------------------- # UI styling # ---------------------------- st.markdown( ''' ''', unsafe_allow_html=True ) # ---------------------------- # Session state initialization # ---------------------------- if "member_df" not in st.session_state: st.session_state.member_df = pd.DataFrame([ {"Name": "Abbas", "Family Position": "Father"}, {"Name": "Sara", "Family Position": "Mother"}, {"Name": "Bilal", "Family Position": "Son"}, {"Name": "Ayesha", "Family Position": "Daughter"}, ]) if "income_sources" not in st.session_state: st.session_state.income_sources = ["Salary", "Business", "Freelance", "Bonus", "Rent Income", "Other"] if "category_map" not in st.session_state: st.session_state.category_map = { "Groceries": ["Vegetables", "Meat", "General Store"], "Utilities": ["Electricity", "Gas", "Internet", "Water"], "Rent": ["House Rent"], "School Fees": ["Tuition Fee", "Books", "Transport Fee"], "Transport": ["Fuel", "Ride Fare", "Maintenance"], "Medical": ["Medicine", "Doctor", "Lab Tests"], "Entertainment": ["Family Dinner", "Movies", "Outing"], "Household": ["Cleaning", "Maintenance", "Furniture"], "Other": ["Miscellaneous"], } if "income_df" not in st.session_state: st.session_state.income_df = pd.DataFrame(columns=["ID", "Date", "Member", "Source", "Amount", "Notes"]) if "expense_df" not in st.session_state: st.session_state.expense_df = pd.DataFrame(columns=[ "ID", "Date", "Expense Type", "Paid By", "Participants", "Category", "Subcategory", "Amount", "Description", "Month" ]) if "budget_df" not in st.session_state: st.session_state.budget_df = pd.DataFrame([ {"Category": "Groceries", "Monthly Budget": 30000}, {"Category": "Utilities", "Monthly Budget": 15000}, {"Category": "Rent", "Monthly Budget": 50000}, {"Category": "School Fees", "Monthly Budget": 20000}, {"Category": "Transport", "Monthly Budget": 10000}, {"Category": "Medical", "Monthly Budget": 10000}, {"Category": "Entertainment", "Monthly Budget": 8000}, {"Category": "Household", "Monthly Budget": 12000}, {"Category": "Other", "Monthly Budget": 10000}, ]) if "dev_notes_df" not in st.session_state: st.session_state.dev_notes_df = pd.DataFrame(columns=["ID", "Date", "Type", "Title", "Details", "Status"]) if "next_income_id" not in st.session_state: st.session_state.next_income_id = 1 if "next_expense_id" not in st.session_state: st.session_state.next_expense_id = 1 if "next_note_id" not in st.session_state: st.session_state.next_note_id = 1 # ---------------------------- # Helpers # ---------------------------- def normalize_month(d): return pd.to_datetime(d).strftime("%Y-%m") def get_member_names(): if st.session_state.member_df.empty: return [] return st.session_state.member_df["Name"].tolist() def add_member(name, family_position): name = name.strip() family_position = family_position.strip() if not name or not family_position: return False, "Enter both name and family position." if name in get_member_names(): return False, "This member already exists." new_row = pd.DataFrame([{ "Name": name, "Family Position": family_position }]) st.session_state.member_df = pd.concat( [st.session_state.member_df, new_row], ignore_index=True ) return True, f"{name} added successfully." def dataframe_to_csv_download(df): return df.to_csv(index=False).encode("utf-8") def load_uploaded_csv(uploaded_file, expected_cols): df = pd.read_csv(uploaded_file) missing = [c for c in expected_cols if c not in df.columns] if missing: raise ValueError(f"Missing columns: {', '.join(missing)}") return df[expected_cols] def format_df(df): config = {} if "Amount" in df.columns: config["Amount"] = st.column_config.NumberColumn(format="%,d") if "Monthly Budget" in df.columns: config["Monthly Budget"] = st.column_config.NumberColumn(format="%,d") if "Spent" in df.columns: config["Spent"] = st.column_config.NumberColumn(format="%,d") if "Remaining" in df.columns: config["Remaining"] = st.column_config.NumberColumn(format="%,d") return config # ---------------------------- # Sidebar # ---------------------------- st.sidebar.title("๐Ÿ’ฐ Family Expense Manager") page = st.sidebar.radio( "Go to", ["Dashboard", "Add Income", "Add Expense", "Monthly Budget", "Masters", "Reports", "Development Notes", "Data Backup"] ) st.sidebar.markdown("---") new_member_name = st.sidebar.text_input("Member Name") new_member_position = st.sidebar.selectbox( "Family Position", ["Father", "Mother", "Son", "Daughter", "Brother", "Sister", "Grandfather", "Grandmother", "Other"] ) if st.sidebar.button("Add Member"): success, message = add_member(new_member_name, new_member_position) if success: st.sidebar.success(message) else: st.sidebar.warning(message) st.sidebar.write("**Members:**") if not st.session_state.member_df.empty: for _, row in st.session_state.member_df.iterrows(): st.sidebar.write(f"โ€ข {row['Name']} ({row['Family Position']})") else: st.sidebar.write("No members added yet.") # ---------------------------- # Dashboard # ---------------------------- if page == "Dashboard": st.title("๐Ÿ  Family Expense Dashboard") income_df = st.session_state.income_df.copy() expense_df = st.session_state.expense_df.copy() budget_df = st.session_state.budget_df.copy() total_income = int(income_df["Amount"].sum()) if not income_df.empty else 0 total_expense = int(expense_df["Amount"].sum()) if not expense_df.empty else 0 balance = total_income - total_expense c1, c2, c3 = st.columns(3) c1.metric("Total Income", f"Rs. {total_income:,}") c2.metric("Total Expenses", f"Rs. {total_expense:,}") c3.metric("Net Balance", f"Rs. {balance:,}") st.markdown("### This Month Snapshot") current_month = date.today().strftime("%Y-%m") month_income = int(income_df[income_df["Date"].astype(str).str[:7] == current_month]["Amount"].sum()) if not income_df.empty else 0 month_expense = int(expense_df[expense_df["Month"] == current_month]["Amount"].sum()) if not expense_df.empty else 0 month_budget = int(budget_df["Monthly Budget"].sum()) if not budget_df.empty else 0 c4, c5, c6 = st.columns(3) c4.metric("This Month Income", f"Rs. {month_income:,}") c5.metric("This Month Expense", f"Rs. {month_expense:,}") c6.metric("Monthly Budget", f"Rs. {month_budget:,}") st.markdown("### Family Members") st.dataframe(st.session_state.member_df, use_container_width=True, hide_index=True) st.markdown("### Category Wise Expense") if not expense_df.empty: by_category = expense_df.groupby("Category", as_index=False)["Amount"].sum().sort_values("Amount", ascending=False) st.bar_chart(by_category.set_index("Category")) st.dataframe(by_category, use_container_width=True, hide_index=True, column_config=format_df(by_category)) else: st.info("No expenses added yet.") st.markdown("### Expense by Family Member") if not expense_df.empty: by_member = expense_df.groupby("Paid By", as_index=False)["Amount"].sum().sort_values("Amount", ascending=False) st.bar_chart(by_member.set_index("Paid By")) st.dataframe(by_member, use_container_width=True, hide_index=True, column_config=format_df(by_member)) else: st.info("No member expense data yet.") st.markdown("### Combined vs Individual Expense") if not expense_df.empty: by_type = expense_df.groupby("Expense Type", as_index=False)["Amount"].sum() st.bar_chart(by_type.set_index("Expense Type")) st.dataframe(by_type, use_container_width=True, hide_index=True, column_config=format_df(by_type)) # ---------------------------- # Add Income # ---------------------------- elif page == "Add Income": st.title("โž• Add Income") with st.form("income_form", clear_on_submit=True): income_date = st.date_input("Date", value=date.today()) member = st.selectbox("Member", get_member_names()) source = st.selectbox("Income Source", st.session_state.income_sources) amount = st.number_input("Amount", min_value=0, step=100, format="%d") notes = st.text_area("Notes") submit_income = st.form_submit_button("Save Income") if submit_income: if amount <= 0: st.error("Amount must be greater than zero.") else: new_row = pd.DataFrame([{ "ID": st.session_state.next_income_id, "Date": str(income_date), "Member": member, "Source": source, "Amount": int(amount), "Notes": notes }]) st.session_state.income_df = pd.concat([st.session_state.income_df, new_row], ignore_index=True) st.session_state.next_income_id += 1 st.success("Income added successfully.") st.markdown("### Income Records") st.dataframe(st.session_state.income_df, use_container_width=True, hide_index=True, column_config=format_df(st.session_state.income_df)) if not st.session_state.income_df.empty: st.markdown("### Edit or Delete Income") selected_income_id = st.selectbox("Select Income Record", st.session_state.income_df["ID"].tolist()) selected_income = st.session_state.income_df[st.session_state.income_df["ID"] == selected_income_id].iloc[0] col1, col2 = st.columns([3, 1]) with col1: with st.form("edit_income_form"): edit_income_date = st.date_input("Edit Date", value=pd.to_datetime(selected_income["Date"]).date()) member_names = get_member_names() member_index = member_names.index(selected_income["Member"]) if selected_income["Member"] in member_names else 0 edit_member = st.selectbox("Edit Member", member_names, index=member_index) source_index = st.session_state.income_sources.index(selected_income["Source"]) if selected_income["Source"] in st.session_state.income_sources else 0 edit_source = st.selectbox("Edit Income Source", st.session_state.income_sources, index=source_index) edit_amount = st.number_input("Edit Amount", min_value=0, step=100, format="%d", value=int(selected_income["Amount"])) edit_notes = st.text_area("Edit Notes", value=selected_income["Notes"]) update_income = st.form_submit_button("Update Income") if update_income: idx = st.session_state.income_df.index[st.session_state.income_df["ID"] == selected_income_id][0] st.session_state.income_df.at[idx, "Date"] = str(edit_income_date) st.session_state.income_df.at[idx, "Member"] = edit_member st.session_state.income_df.at[idx, "Source"] = edit_source st.session_state.income_df.at[idx, "Amount"] = int(edit_amount) st.session_state.income_df.at[idx, "Notes"] = edit_notes st.success("Income updated successfully.") with col2: st.write("") st.write("") if st.button("Delete Income", type="primary"): st.session_state.income_df = st.session_state.income_df[st.session_state.income_df["ID"] != selected_income_id].reset_index(drop=True) st.success("Income deleted successfully.") st.rerun() # ---------------------------- # Add Expense # ---------------------------- elif page == "Add Expense": st.title("๐Ÿงพ Add Expense") categories = list(st.session_state.category_map.keys()) member_names = get_member_names() with st.form("expense_form", clear_on_submit=True): exp_date = st.date_input("Date", value=date.today()) expense_type = st.radio("Expense Type", ["Individual", "Combined"], horizontal=True) paid_by = st.selectbox("Paid By", member_names) default_participants = [paid_by] if expense_type == "Individual" else member_names participants = st.multiselect("Participants", member_names, default=default_participants) category = st.selectbox("Category", categories) subcategory = st.selectbox("Subcategory", st.session_state.category_map.get(category, ["Miscellaneous"])) amount = st.number_input("Amount", min_value=0, step=100, format="%d") description = st.text_area("Description") submit_expense = st.form_submit_button("Save Expense") if submit_expense: if amount <= 0: st.error("Amount must be greater than zero.") elif expense_type == "Combined" and not participants: st.error("Please select at least one participant for combined expense.") else: if expense_type == "Individual": participants = [paid_by] new_row = pd.DataFrame([{ "ID": st.session_state.next_expense_id, "Date": str(exp_date), "Expense Type": expense_type, "Paid By": paid_by, "Participants": ", ".join(participants), "Category": category, "Subcategory": subcategory, "Amount": int(amount), "Description": description, "Month": normalize_month(exp_date) }]) st.session_state.expense_df = pd.concat([st.session_state.expense_df, new_row], ignore_index=True) st.session_state.next_expense_id += 1 st.success("Expense added successfully.") st.markdown("### Expense Records") st.dataframe(st.session_state.expense_df, use_container_width=True, hide_index=True, column_config=format_df(st.session_state.expense_df)) if not st.session_state.expense_df.empty: st.markdown("### Edit or Delete Expense") selected_expense_id = st.selectbox("Select Expense Record", st.session_state.expense_df["ID"].tolist()) selected_expense = st.session_state.expense_df[st.session_state.expense_df["ID"] == selected_expense_id].iloc[0] existing_participants = [p.strip() for p in str(selected_expense["Participants"]).split(",") if p.strip()] category_index = categories.index(selected_expense["Category"]) if selected_expense["Category"] in categories else 0 col1, col2 = st.columns([3, 1]) with col1: with st.form("edit_expense_form"): edit_exp_date = st.date_input("Edit Date", value=pd.to_datetime(selected_expense["Date"]).date()) edit_expense_type = st.radio( "Edit Expense Type", ["Individual", "Combined"], index=0 if selected_expense["Expense Type"] == "Individual" else 1, horizontal=True ) paid_by_index = member_names.index(selected_expense["Paid By"]) if selected_expense["Paid By"] in member_names else 0 edit_paid_by = st.selectbox("Edit Paid By", member_names, index=paid_by_index) edit_participants = st.multiselect("Edit Participants", member_names, default=existing_participants) edit_category = st.selectbox("Edit Category", categories, index=category_index) subcategory_options = st.session_state.category_map.get(edit_category, ["Miscellaneous"]) sub_index = subcategory_options.index(selected_expense["Subcategory"]) if selected_expense["Subcategory"] in subcategory_options else 0 edit_subcategory = st.selectbox("Edit Subcategory", subcategory_options, index=sub_index) edit_amount = st.number_input("Edit Amount", min_value=0, step=100, format="%d", value=int(selected_expense["Amount"])) edit_description = st.text_area("Edit Description", value=selected_expense["Description"]) update_expense = st.form_submit_button("Update Expense") if update_expense: if edit_expense_type == "Combined" and not edit_participants: st.error("Please select at least one participant for combined expense.") else: if edit_expense_type == "Individual": edit_participants = [edit_paid_by] idx = st.session_state.expense_df.index[st.session_state.expense_df["ID"] == selected_expense_id][0] st.session_state.expense_df.at[idx, "Date"] = str(edit_exp_date) st.session_state.expense_df.at[idx, "Expense Type"] = edit_expense_type st.session_state.expense_df.at[idx, "Paid By"] = edit_paid_by st.session_state.expense_df.at[idx, "Participants"] = ", ".join(edit_participants) st.session_state.expense_df.at[idx, "Category"] = edit_category st.session_state.expense_df.at[idx, "Subcategory"] = edit_subcategory st.session_state.expense_df.at[idx, "Amount"] = int(edit_amount) st.session_state.expense_df.at[idx, "Description"] = edit_description st.session_state.expense_df.at[idx, "Month"] = normalize_month(edit_exp_date) st.success("Expense updated successfully.") with col2: st.write("") st.write("") if st.button("Delete Expense", type="primary"): st.session_state.expense_df = st.session_state.expense_df[st.session_state.expense_df["ID"] != selected_expense_id].reset_index(drop=True) st.success("Expense deleted successfully.") st.rerun() # ---------------------------- # Monthly Budget # ---------------------------- elif page == "Monthly Budget": st.title("๐Ÿ“… Monthly Budget") budget_df = st.session_state.budget_df.copy() edited_budget = st.data_editor( budget_df, num_rows="dynamic", use_container_width=True, key="budget_editor", hide_index=True, column_config={ "Monthly Budget": st.column_config.NumberColumn(format="%,d") } ) col1, col2 = st.columns([1, 1]) with col1: if st.button("Save Budget"): edited_budget["Monthly Budget"] = pd.to_numeric(edited_budget["Monthly Budget"], errors="coerce").fillna(0).astype(int) st.session_state.budget_df = edited_budget st.success("Budget updated successfully.") with col2: if not st.session_state.budget_df.empty: delete_budget_category = st.selectbox("Delete Budget Row", st.session_state.budget_df["Category"].tolist()) if st.button("Delete Budget Category"): st.session_state.budget_df = st.session_state.budget_df[ st.session_state.budget_df["Category"] != delete_budget_category ].reset_index(drop=True) st.success("Budget row deleted successfully.") st.rerun() st.markdown("### Budget vs Expense") expense_df = st.session_state.expense_df.copy() current_month = date.today().strftime("%Y-%m") if not expense_df.empty: this_month_expense = expense_df[expense_df["Month"] == current_month].groupby("Category", as_index=False)["Amount"].sum() else: this_month_expense = pd.DataFrame(columns=["Category", "Amount"]) merged = st.session_state.budget_df.merge(this_month_expense, on="Category", how="left") merged["Amount"] = merged["Amount"].fillna(0).astype(int) merged["Remaining"] = (merged["Monthly Budget"] - merged["Amount"]).astype(int) merged = merged.rename(columns={"Amount": "Spent"}) st.dataframe(merged, use_container_width=True, hide_index=True, column_config=format_df(merged)) # ---------------------------- # Masters # ---------------------------- elif page == "Masters": st.title("๐Ÿ› ๏ธ Masters") tab1, tab2, tab3 = st.tabs(["Family Members", "Income Sources", "Categories & Subcategories"]) with tab1: st.subheader("Family Members") st.caption("At least one family member is compulsory.") col1, col2 = st.columns(2) with col1: new_name = st.text_input("New Family Member Name") new_position = st.selectbox( "New Family Position", ["Father", "Mother", "Son", "Daughter", "Brother", "Sister", "Grandfather", "Grandmother", "Other"], key="masters_new_position" ) if st.button("Add Family Member"): success, message = add_member(new_name, new_position) if success: st.success(message) st.rerun() else: st.warning(message) with col2: member_names = get_member_names() selected_member_name = st.selectbox("Delete Family Member", member_names) if st.button("Delete Family Member"): if len(st.session_state.member_df) <= 1: st.error("At least one family member must remain.") else: st.session_state.member_df = st.session_state.member_df[ st.session_state.member_df["Name"] != selected_member_name ].reset_index(drop=True) st.success("Family member deleted successfully.") st.rerun() st.markdown("### Rename / Update Family Position") member_names = get_member_names() selected_edit_member = st.selectbox("Select Family Member", member_names, key="edit_family_member") selected_row = st.session_state.member_df[ st.session_state.member_df["Name"] == selected_edit_member ].iloc[0] edit_name = st.text_input("Edit Name", value=selected_row["Name"]) family_positions = ["Father", "Mother", "Son", "Daughter", "Brother", "Sister", "Grandfather", "Grandmother", "Other"] edit_position = st.selectbox( "Edit Family Position", family_positions, index=family_positions.index(selected_row["Family Position"]) if selected_row["Family Position"] in family_positions else 0, key="edit_family_position" ) if st.button("Update Family Member"): edit_name = edit_name.strip() if not edit_name: st.warning("Name cannot be empty.") else: old_name = selected_row["Name"] duplicate_name = st.session_state.member_df[ (st.session_state.member_df["Name"] == edit_name) & (st.session_state.member_df["Name"] != old_name) ] if not duplicate_name.empty: st.warning("Another family member already has this name.") else: idx = st.session_state.member_df.index[ st.session_state.member_df["Name"] == old_name ][0] st.session_state.member_df.at[idx, "Name"] = edit_name st.session_state.member_df.at[idx, "Family Position"] = edit_position if not st.session_state.income_df.empty: st.session_state.income_df.loc[ st.session_state.income_df["Member"] == old_name, "Member" ] = edit_name if not st.session_state.expense_df.empty: st.session_state.expense_df.loc[ st.session_state.expense_df["Paid By"] == old_name, "Paid By" ] = edit_name st.session_state.expense_df["Participants"] = st.session_state.expense_df["Participants"].astype(str).str.replace( old_name, edit_name, regex=False ) st.success("Family member updated successfully.") st.rerun() st.dataframe(st.session_state.member_df, use_container_width=True, hide_index=True) with tab2: st.subheader("Income Sources") col1, col2 = st.columns(2) with col1: new_income_source = st.text_input("New Income Source") if st.button("Add Income Source"): if not new_income_source.strip(): st.warning("Enter a valid income source.") elif new_income_source.strip() in st.session_state.income_sources: st.warning("This income source already exists.") else: st.session_state.income_sources.append(new_income_source.strip()) st.success("Income source added successfully.") st.rerun() with col2: selected_source = st.selectbox("Delete Income Source", st.session_state.income_sources) if st.button("Delete Income Source"): if len(st.session_state.income_sources) <= 1: st.error("At least one income source must remain.") else: st.session_state.income_sources.remove(selected_source) st.success("Income source deleted successfully.") st.rerun() rename_source = st.selectbox("Rename Income Source", st.session_state.income_sources) renamed_value = st.text_input("New Name for Selected Source") if st.button("Update Income Source Name"): if not renamed_value.strip(): st.warning("Enter a valid new name.") elif renamed_value.strip() in st.session_state.income_sources: st.warning("This income source already exists.") else: idx = st.session_state.income_sources.index(rename_source) st.session_state.income_sources[idx] = renamed_value.strip() st.success("Income source renamed successfully.") st.rerun() st.dataframe(pd.DataFrame({"Income Source": st.session_state.income_sources}), use_container_width=True, hide_index=True) with tab3: st.subheader("Categories & Subcategories") categories = list(st.session_state.category_map.keys()) col1, col2 = st.columns(2) with col1: new_category = st.text_input("New Category") if st.button("Add Category"): if not new_category.strip(): st.warning("Enter a valid category.") elif new_category.strip() in st.session_state.category_map: st.warning("This category already exists.") else: st.session_state.category_map[new_category.strip()] = ["Miscellaneous"] st.success("Category added successfully.") st.rerun() with col2: delete_category = st.selectbox("Delete Category", categories) if st.button("Delete Category"): if len(st.session_state.category_map) <= 1: st.error("At least one category must remain.") else: del st.session_state.category_map[delete_category] st.success("Category deleted successfully.") st.rerun() rename_category = st.selectbox("Rename Category", categories) renamed_category_value = st.text_input("New Category Name") if st.button("Update Category Name"): if not renamed_category_value.strip(): st.warning("Enter a valid category name.") elif renamed_category_value.strip() in st.session_state.category_map: st.warning("This category already exists.") else: st.session_state.category_map[renamed_category_value.strip()] = st.session_state.category_map.pop(rename_category) st.success("Category renamed successfully.") st.rerun() st.markdown("---") current_category = st.selectbox("Manage Subcategories For", list(st.session_state.category_map.keys())) current_subcategories = st.session_state.category_map[current_category] sub1, sub2 = st.columns(2) with sub1: new_subcategory = st.text_input("New Subcategory") if st.button("Add Subcategory"): if not new_subcategory.strip(): st.warning("Enter a valid subcategory.") elif new_subcategory.strip() in st.session_state.category_map[current_category]: st.warning("This subcategory already exists.") else: st.session_state.category_map[current_category].append(new_subcategory.strip()) st.success("Subcategory added successfully.") st.rerun() with sub2: delete_subcategory = st.selectbox("Delete Subcategory", current_subcategories) if st.button("Delete Subcategory"): if len(st.session_state.category_map[current_category]) <= 1: st.error("At least one subcategory must remain in a category.") else: st.session_state.category_map[current_category].remove(delete_subcategory) st.success("Subcategory deleted successfully.") st.rerun() rename_subcategory = st.selectbox("Rename Subcategory", current_subcategories) renamed_subcategory_value = st.text_input("New Subcategory Name") if st.button("Update Subcategory Name"): if not renamed_subcategory_value.strip(): st.warning("Enter a valid subcategory name.") elif renamed_subcategory_value.strip() in st.session_state.category_map[current_category]: st.warning("This subcategory already exists.") else: idx = st.session_state.category_map[current_category].index(rename_subcategory) st.session_state.category_map[current_category][idx] = renamed_subcategory_value.strip() st.success("Subcategory renamed successfully.") st.rerun() flat_rows = [] for cat, subs in st.session_state.category_map.items(): for sub in subs: flat_rows.append({"Category": cat, "Subcategory": sub}) st.dataframe(pd.DataFrame(flat_rows), use_container_width=True, hide_index=True) # ---------------------------- # Reports # ---------------------------- elif page == "Reports": st.title("๐Ÿ“Š Reports") expense_df = st.session_state.expense_df.copy() income_df = st.session_state.income_df.copy() if expense_df.empty and income_df.empty: st.info("No data available yet.") else: st.markdown("### Filter Reports") col1, col2, col3 = st.columns(3) with col1: report_month = st.text_input("Month (YYYY-MM)", value=date.today().strftime("%Y-%m")) with col2: member_filter = st.selectbox("Member", ["All"] + get_member_names()) with col3: category_filter = st.selectbox("Category", ["All"] + list(st.session_state.category_map.keys())) filtered_expense = expense_df.copy() filtered_income = income_df.copy() if not filtered_expense.empty: filtered_expense = filtered_expense[filtered_expense["Month"] == report_month] if not filtered_income.empty: filtered_income = filtered_income[filtered_income["Date"].astype(str).str[:7] == report_month] if member_filter != "All": if not filtered_expense.empty: filtered_expense = filtered_expense[filtered_expense["Paid By"] == member_filter] if not filtered_income.empty: filtered_income = filtered_income[filtered_income["Member"] == member_filter] if category_filter != "All" and not filtered_expense.empty: filtered_expense = filtered_expense[filtered_expense["Category"] == category_filter] total_income = int(filtered_income["Amount"].sum()) if not filtered_income.empty else 0 total_expense = int(filtered_expense["Amount"].sum()) if not filtered_expense.empty else 0 savings = total_income - total_expense r1, r2, r3 = st.columns(3) r1.metric("Report Income", f"Rs. {total_income:,}") r2.metric("Report Expense", f"Rs. {total_expense:,}") r3.metric("Savings", f"Rs. {savings:,}") st.markdown("### Expense Table") st.dataframe(filtered_expense, use_container_width=True, hide_index=True, column_config=format_df(filtered_expense)) st.markdown("### Income Table") st.dataframe(filtered_income, use_container_width=True, hide_index=True, column_config=format_df(filtered_income)) if not filtered_expense.empty: st.markdown("### Category Summary") summary = filtered_expense.groupby("Category", as_index=False)["Amount"].sum().sort_values("Amount", ascending=False) st.dataframe(summary, use_container_width=True, hide_index=True, column_config=format_df(summary)) st.bar_chart(summary.set_index("Category")) st.markdown("### Expense Type Summary") type_summary = filtered_expense.groupby("Expense Type", as_index=False)["Amount"].sum() st.dataframe(type_summary, use_container_width=True, hide_index=True, column_config=format_df(type_summary)) st.bar_chart(type_summary.set_index("Expense Type")) # ---------------------------- # Development Notes # ---------------------------- elif page == "Development Notes": st.title("๐Ÿ“ Development Notes") with st.form("dev_note_form", clear_on_submit=True): note_date = st.date_input("Date", value=date.today()) note_type = st.selectbox("Type", ["Requirement", "Bug", "Improvement", "Idea", "Task"]) title = st.text_input("Title") details = st.text_area("Details") status = st.selectbox("Status", ["Open", "In Progress", "Completed", "Hold"]) submit_note = st.form_submit_button("Add Note") if submit_note: if not title.strip(): st.error("Title is required.") else: new_row = pd.DataFrame([{ "ID": st.session_state.next_note_id, "Date": str(note_date), "Type": note_type, "Title": title.strip(), "Details": details.strip(), "Status": status }]) st.session_state.dev_notes_df = pd.concat([st.session_state.dev_notes_df, new_row], ignore_index=True) st.session_state.next_note_id += 1 st.success("Development note added successfully.") st.markdown("### Notes List") st.dataframe(st.session_state.dev_notes_df, use_container_width=True, hide_index=True) if not st.session_state.dev_notes_df.empty: st.markdown("### Edit or Delete Note") selected_note_id = st.selectbox("Select Note", st.session_state.dev_notes_df["ID"].tolist()) selected_note = st.session_state.dev_notes_df[st.session_state.dev_notes_df["ID"] == selected_note_id].iloc[0] type_options = ["Requirement", "Bug", "Improvement", "Idea", "Task"] status_options = ["Open", "In Progress", "Completed", "Hold"] col1, col2 = st.columns([3, 1]) with col1: with st.form("edit_note_form"): edit_note_date = st.date_input("Edit Date", value=pd.to_datetime(selected_note["Date"]).date()) edit_note_type = st.selectbox("Edit Type", type_options, index=type_options.index(selected_note["Type"])) edit_title = st.text_input("Edit Title", value=selected_note["Title"]) edit_details = st.text_area("Edit Details", value=selected_note["Details"]) edit_status = st.selectbox("Edit Status", status_options, index=status_options.index(selected_note["Status"])) update_note = st.form_submit_button("Update Note") if update_note: if not edit_title.strip(): st.error("Title is required.") else: idx = st.session_state.dev_notes_df.index[st.session_state.dev_notes_df["ID"] == selected_note_id][0] st.session_state.dev_notes_df.at[idx, "Date"] = str(edit_note_date) st.session_state.dev_notes_df.at[idx, "Type"] = edit_note_type st.session_state.dev_notes_df.at[idx, "Title"] = edit_title.strip() st.session_state.dev_notes_df.at[idx, "Details"] = edit_details.strip() st.session_state.dev_notes_df.at[idx, "Status"] = edit_status st.success("Development note updated successfully.") with col2: st.write("") st.write("") if st.button("Delete Note", type="primary"): st.session_state.dev_notes_df = st.session_state.dev_notes_df[st.session_state.dev_notes_df["ID"] != selected_note_id].reset_index(drop=True) st.success("Development note deleted successfully.") st.rerun() # ---------------------------- # Data Backup # ---------------------------- elif page == "Data Backup": st.title("๐Ÿ’พ Data Backup") st.markdown("### Download Current Data") st.download_button( "Download Family Members CSV", data=dataframe_to_csv_download(st.session_state.member_df), file_name="family_members.csv", mime="text/csv" ) st.download_button( "Download Income CSV", data=dataframe_to_csv_download(st.session_state.income_df), file_name="income_data.csv", mime="text/csv" ) st.download_button( "Download Expense CSV", data=dataframe_to_csv_download(st.session_state.expense_df), file_name="expense_data.csv", mime="text/csv" ) st.download_button( "Download Budget CSV", data=dataframe_to_csv_download(st.session_state.budget_df), file_name="budget_data.csv", mime="text/csv" ) st.download_button( "Download Development Notes CSV", data=dataframe_to_csv_download(st.session_state.dev_notes_df), file_name="development_notes.csv", mime="text/csv" ) st.markdown("### Restore from CSV") uploaded_members = st.file_uploader("Upload Family Members CSV", type=["csv"], key="members_upload") uploaded_income = st.file_uploader("Upload Income CSV", type=["csv"], key="income_upload") uploaded_expense = st.file_uploader("Upload Expense CSV", type=["csv"], key="expense_upload") uploaded_budget = st.file_uploader("Upload Budget CSV", type=["csv"], key="budget_upload") uploaded_notes = st.file_uploader("Upload Development Notes CSV", type=["csv"], key="notes_upload") if st.button("Restore Uploaded Data"): try: if uploaded_members is not None: st.session_state.member_df = load_uploaded_csv(uploaded_members, ["Name", "Family Position"]) if uploaded_income is not None: st.session_state.income_df = load_uploaded_csv(uploaded_income, ["ID", "Date", "Member", "Source", "Amount", "Notes"]) if uploaded_expense is not None: st.session_state.expense_df = load_uploaded_csv( uploaded_expense, ["ID", "Date", "Expense Type", "Paid By", "Participants", "Category", "Subcategory", "Amount", "Description", "Month"] ) if uploaded_budget is not None: st.session_state.budget_df = load_uploaded_csv(uploaded_budget, ["Category", "Monthly Budget"]) if uploaded_notes is not None: st.session_state.dev_notes_df = load_uploaded_csv(uploaded_notes, ["ID", "Date", "Type", "Title", "Details", "Status"]) if not st.session_state.income_df.empty: st.session_state.next_income_id = int(pd.to_numeric(st.session_state.income_df["ID"], errors="coerce").fillna(0).max()) + 1 if not st.session_state.expense_df.empty: st.session_state.next_expense_id = int(pd.to_numeric(st.session_state.expense_df["ID"], errors="coerce").fillna(0).max()) + 1 if not st.session_state.dev_notes_df.empty: st.session_state.next_note_id = int(pd.to_numeric(st.session_state.dev_notes_df["ID"], errors="coerce").fillna(0).max()) + 1 st.success("Data restored successfully.") except Exception as e: st.error(f"Restore failed: {e}")