manaskhan commited on
Commit
798afe4
Β·
verified Β·
1 Parent(s): b625759

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +68 -22
app.py CHANGED
@@ -4,6 +4,21 @@ import plotly.express as px
4
  from io import StringIO
5
  from datetime import date
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  # -------------------------------
8
  # Page config & CSS
9
  # -------------------------------
@@ -35,13 +50,16 @@ st.markdown(
35
  border: 1px solid rgba(255,255,255,0.06);
36
  box-shadow: 0 6px 18px rgba(0,0,0,0.3);
37
  }
 
 
 
38
  </style>
39
  """,
40
  unsafe_allow_html=True,
41
  )
42
 
43
  st.title("πŸ’Έ Expensive Tracker")
44
- st.caption("Track expenses, analyze spending by category and over time β€” attractive, simple, and portable.")
45
 
46
  # -------------------------------
47
  # Initialize in-memory storage
@@ -52,6 +70,10 @@ if "expenses" not in st.session_state:
52
  columns=["Date", "Category", "Amount", "Notes"]
53
  )
54
 
 
 
 
 
55
  # -------------------------------
56
  # Sidebar navigation
57
  # -------------------------------
@@ -65,7 +87,7 @@ CATEGORIES = ["Food", "Travel", "Shopping", "Bills", "Entertainment", "Health",
65
  # Add Expense
66
  # -------------------------------
67
  if page == "Add Expense":
68
- st.header("Add a new expense")
69
  with st.form("add_expense_form", clear_on_submit=True):
70
  c1, c2, c3 = st.columns([1, 1, 1])
71
  with c1:
@@ -73,36 +95,46 @@ if page == "Add Expense":
73
  with c2:
74
  category = st.selectbox("Category", options=CATEGORIES)
75
  with c3:
76
- amount = st.number_input("Amount (₨ or your currency)", min_value=0.0, format="%.2f")
 
77
  notes = st.text_area("Notes (optional)", max_chars=200, placeholder="Where/what for?")
78
  submitted = st.form_submit_button("βž• Add Expense")
79
 
80
  if submitted:
81
- new_row = {"Date": pd.to_datetime(exp_date).date(), "Category": category, "Amount": float(amount), "Notes": notes}
 
 
 
 
 
82
  st.session_state.expenses = pd.concat([st.session_state.expenses, pd.DataFrame([new_row])], ignore_index=True)
83
- st.success("Expense added βœ…")
84
  st.balloons()
85
 
86
  if not st.session_state.expenses.empty:
87
  st.markdown("**Quick preview of latest expenses**")
88
- st.dataframe(st.session_state.expenses.tail(6).reset_index(drop=True))
 
 
 
89
 
90
  # -------------------------------
91
  # View Expenses
92
  # -------------------------------
93
  elif page == "View Expenses":
94
- st.header("All Expenses")
95
  if st.session_state.expenses.empty:
96
  st.info("No expenses yet β€” add some from the 'Add Expense' tab.")
97
  else:
98
  df = st.session_state.expenses.copy()
 
99
  # Allow filtering
100
  st.markdown("Filter")
101
  cols = st.columns([1, 1, 1])
102
  with cols[0]:
103
- min_date = st.date_input("From", value=df["Date"].min())
104
  with cols[1]:
105
- max_date = st.date_input("To", value=df["Date"].max())
106
  with cols[2]:
107
  sel_cat = st.multiselect("Category", options=["All"] + CATEGORIES, default=["All"])
108
  filtered = df[
@@ -112,7 +144,9 @@ elif page == "View Expenses":
112
  if sel_cat and "All" not in sel_cat:
113
  filtered = filtered[filtered["Category"].isin(sel_cat)]
114
 
115
- st.dataframe(filtered.sort_values(by="Date", ascending=False).reset_index(drop=True))
 
 
116
 
117
  # Option to delete last entry or clear all
118
  st.markdown("---")
@@ -130,53 +164,62 @@ elif page == "View Expenses":
130
  # Summary Dashboard
131
  # -------------------------------
132
  elif page == "Summary":
133
- st.header("Summary Dashboard")
134
  if st.session_state.expenses.empty:
135
  st.info("No data yet β€” add expenses to see the summary.")
136
  else:
137
  df = st.session_state.expenses.copy()
138
  df["Date"] = pd.to_datetime(df["Date"])
 
 
139
  total = df["Amount"].sum()
140
  avg = df["Amount"].mean()
141
  max_exp = df["Amount"].max()
142
  st.markdown("<div class='card'>", unsafe_allow_html=True)
143
  c1, c2, c3 = st.columns(3)
144
- c1.metric("Total Spent", f"{total:,.2f}")
145
- c2.metric("Average Expense", f"{avg:,.2f}")
146
- c3.metric("Largest Expense", f"{max_exp:,.2f}")
147
  st.markdown("</div>", unsafe_allow_html=True)
148
 
149
  st.markdown("### πŸ“Š Expenses by Category")
150
  cat_summary = df.groupby("Category", as_index=False)["Amount"].sum().sort_values("Amount", ascending=False)
 
151
  fig_pie = px.pie(cat_summary, names="Category", values="Amount", title="Spending by Category", hole=0.4)
 
152
  st.plotly_chart(fig_pie, use_container_width=True)
153
 
154
  st.markdown("### πŸ•’ Expenses Over Time")
155
  timeseries = df.groupby(pd.Grouper(key="Date", freq="D"))["Amount"].sum().reset_index()
156
- # Fill missing days for smooth line
157
  timeseries = timeseries.set_index("Date").resample("D").sum().fillna(0).reset_index()
158
  fig_line = px.bar(timeseries, x="Date", y="Amount", title="Daily Spending (bar)")
 
159
  st.plotly_chart(fig_line, use_container_width=True)
160
 
161
  st.markdown("### πŸ”Ž Top 5 Expenses")
162
- st.dataframe(df.nlargest(5, "Amount")[["Date", "Category", "Amount", "Notes"]].reset_index(drop=True))
 
 
163
 
164
  # -------------------------------
165
  # Import / Export
166
  # -------------------------------
167
  elif page == "Import / Export":
168
- st.header("Import or Export your data")
169
- st.markdown("You can download your current expenses as a CSV or upload a CSV to load expenses.")
170
 
171
  # Download
172
  if st.session_state.expenses.empty:
173
  st.info("No expenses to export.")
174
  else:
175
- csv = st.session_state.expenses.to_csv(index=False)
 
 
 
176
  st.download_button("⬇️ Download CSV", data=csv, file_name="expenses.csv", mime="text/csv")
177
 
178
  st.markdown("---")
179
- st.markdown("Upload a CSV file (columns: Date, Category, Amount, Notes)")
180
  uploaded = st.file_uploader("Upload CSV", type=["csv"])
181
  if uploaded is not None:
182
  try:
@@ -186,8 +229,11 @@ elif page == "Import / Export":
186
  if not required.issubset(set(uploaded_df.columns)):
187
  st.error("CSV must include at least Date, Category, and Amount columns.")
188
  else:
189
- # Append uploaded
190
- uploaded_df = uploaded_df[["Date", "Category", "Amount", "Notes"]] if "Notes" in uploaded_df.columns else uploaded_df.assign(Notes="")
 
 
 
191
  uploaded_df["Date"] = pd.to_datetime(uploaded_df["Date"]).dt.date
192
  st.session_state.expenses = pd.concat([st.session_state.expenses, uploaded_df], ignore_index=True)
193
  st.success("Uploaded expenses added to your tracker.")
 
4
  from io import StringIO
5
  from datetime import date
6
 
7
+ # -------------------------------
8
+ # Helpers
9
+ # -------------------------------
10
+ def format_rs(amount):
11
+ """Format numeric amount into Rupee string with thousands separators."""
12
+ try:
13
+ # show no decimals for rupee amounts
14
+ return f"Rs {int(round(float(amount))):,}"
15
+ except Exception:
16
+ return str(amount)
17
+
18
+ def ensure_numeric_amount(col):
19
+ """Convert amount column to numeric (int) safely."""
20
+ return pd.to_numeric(col, errors="coerce").fillna(0).astype(int)
21
+
22
  # -------------------------------
23
  # Page config & CSS
24
  # -------------------------------
 
50
  border: 1px solid rgba(255,255,255,0.06);
51
  box-shadow: 0 6px 18px rgba(0,0,0,0.3);
52
  }
53
+ .dataframe td, .dataframe th {
54
+ color: #e6eef8 !important;
55
+ }
56
  </style>
57
  """,
58
  unsafe_allow_html=True,
59
  )
60
 
61
  st.title("πŸ’Έ Expensive Tracker")
62
+ st.caption("Track expenses in Rupees (Rs). Enter whole numbers like 100, 500, 10000.")
63
 
64
  # -------------------------------
65
  # Initialize in-memory storage
 
70
  columns=["Date", "Category", "Amount", "Notes"]
71
  )
72
 
73
+ # Ensure Amount column numeric if loaded previously
74
+ if not st.session_state.expenses.empty:
75
+ st.session_state.expenses["Amount"] = ensure_numeric_amount(st.session_state.expenses["Amount"])
76
+
77
  # -------------------------------
78
  # Sidebar navigation
79
  # -------------------------------
 
87
  # Add Expense
88
  # -------------------------------
89
  if page == "Add Expense":
90
+ st.header("Add a new expense (Amount in Rs)")
91
  with st.form("add_expense_form", clear_on_submit=True):
92
  c1, c2, c3 = st.columns([1, 1, 1])
93
  with c1:
 
95
  with c2:
96
  category = st.selectbox("Category", options=CATEGORIES)
97
  with c3:
98
+ # Integer rupee input
99
+ amount = st.number_input("Amount (Rs)", min_value=0, step=1, format="%d", value=0)
100
  notes = st.text_area("Notes (optional)", max_chars=200, placeholder="Where/what for?")
101
  submitted = st.form_submit_button("βž• Add Expense")
102
 
103
  if submitted:
104
+ new_row = {
105
+ "Date": pd.to_datetime(exp_date).date(),
106
+ "Category": category,
107
+ "Amount": int(amount),
108
+ "Notes": notes
109
+ }
110
  st.session_state.expenses = pd.concat([st.session_state.expenses, pd.DataFrame([new_row])], ignore_index=True)
111
+ st.success(f"Expense added βœ… {format_rs(amount)}")
112
  st.balloons()
113
 
114
  if not st.session_state.expenses.empty:
115
  st.markdown("**Quick preview of latest expenses**")
116
+ preview = st.session_state.expenses.tail(6).reset_index(drop=True).copy()
117
+ # Format Amount column for display
118
+ preview["Amount (Rs)"] = preview["Amount"].apply(format_rs)
119
+ st.dataframe(preview[["Date", "Category", "Amount (Rs)", "Notes"]])
120
 
121
  # -------------------------------
122
  # View Expenses
123
  # -------------------------------
124
  elif page == "View Expenses":
125
+ st.header("All Expenses (Amounts in Rs)")
126
  if st.session_state.expenses.empty:
127
  st.info("No expenses yet β€” add some from the 'Add Expense' tab.")
128
  else:
129
  df = st.session_state.expenses.copy()
130
+ df["Amount"] = ensure_numeric_amount(df["Amount"])
131
  # Allow filtering
132
  st.markdown("Filter")
133
  cols = st.columns([1, 1, 1])
134
  with cols[0]:
135
+ min_date = st.date_input("From", value=pd.to_datetime(df["Date"]).min().date())
136
  with cols[1]:
137
+ max_date = st.date_input("To", value=pd.to_datetime(df["Date"]).max().date())
138
  with cols[2]:
139
  sel_cat = st.multiselect("Category", options=["All"] + CATEGORIES, default=["All"])
140
  filtered = df[
 
144
  if sel_cat and "All" not in sel_cat:
145
  filtered = filtered[filtered["Category"].isin(sel_cat)]
146
 
147
+ display_df = filtered.sort_values(by="Date", ascending=False).reset_index(drop=True).copy()
148
+ display_df["Amount (Rs)"] = display_df["Amount"].apply(format_rs)
149
+ st.dataframe(display_df[["Date", "Category", "Amount (Rs)", "Notes"]])
150
 
151
  # Option to delete last entry or clear all
152
  st.markdown("---")
 
164
  # Summary Dashboard
165
  # -------------------------------
166
  elif page == "Summary":
167
+ st.header("Summary Dashboard (Rs)")
168
  if st.session_state.expenses.empty:
169
  st.info("No data yet β€” add expenses to see the summary.")
170
  else:
171
  df = st.session_state.expenses.copy()
172
  df["Date"] = pd.to_datetime(df["Date"])
173
+ df["Amount"] = ensure_numeric_amount(df["Amount"])
174
+
175
  total = df["Amount"].sum()
176
  avg = df["Amount"].mean()
177
  max_exp = df["Amount"].max()
178
  st.markdown("<div class='card'>", unsafe_allow_html=True)
179
  c1, c2, c3 = st.columns(3)
180
+ c1.metric("Total Spent", format_rs(total))
181
+ c2.metric("Average Expense", format_rs(avg))
182
+ c3.metric("Largest Expense", format_rs(max_exp))
183
  st.markdown("</div>", unsafe_allow_html=True)
184
 
185
  st.markdown("### πŸ“Š Expenses by Category")
186
  cat_summary = df.groupby("Category", as_index=False)["Amount"].sum().sort_values("Amount", ascending=False)
187
+ # For plotly, keep numeric values; labels can show Rs via hover
188
  fig_pie = px.pie(cat_summary, names="Category", values="Amount", title="Spending by Category", hole=0.4)
189
+ fig_pie.update_traces(textinfo="percent+label", hovertemplate="%{label}: Rs %{value:,}<extra></extra>")
190
  st.plotly_chart(fig_pie, use_container_width=True)
191
 
192
  st.markdown("### πŸ•’ Expenses Over Time")
193
  timeseries = df.groupby(pd.Grouper(key="Date", freq="D"))["Amount"].sum().reset_index()
 
194
  timeseries = timeseries.set_index("Date").resample("D").sum().fillna(0).reset_index()
195
  fig_line = px.bar(timeseries, x="Date", y="Amount", title="Daily Spending (bar)")
196
+ fig_line.update_traces(hovertemplate="Date: %{x}<br>Amount: Rs %{y:,}<extra></extra>")
197
  st.plotly_chart(fig_line, use_container_width=True)
198
 
199
  st.markdown("### πŸ”Ž Top 5 Expenses")
200
+ top5 = df.nlargest(5, "Amount")[["Date", "Category", "Amount", "Notes"]].reset_index(drop=True).copy()
201
+ top5["Amount (Rs)"] = top5["Amount"].apply(format_rs)
202
+ st.dataframe(top5[["Date", "Category", "Amount (Rs)", "Notes"]])
203
 
204
  # -------------------------------
205
  # Import / Export
206
  # -------------------------------
207
  elif page == "Import / Export":
208
+ st.header("Import or Export your data (CSV)")
209
+ st.markdown("You can download your current expenses as a CSV or upload a CSV to load expenses. Amounts are stored as integers (Rs).")
210
 
211
  # Download
212
  if st.session_state.expenses.empty:
213
  st.info("No expenses to export.")
214
  else:
215
+ # Ensure numeric amounts before export
216
+ export_df = st.session_state.expenses.copy()
217
+ export_df["Amount"] = ensure_numeric_amount(export_df["Amount"])
218
+ csv = export_df.to_csv(index=False)
219
  st.download_button("⬇️ Download CSV", data=csv, file_name="expenses.csv", mime="text/csv")
220
 
221
  st.markdown("---")
222
+ st.markdown("Upload a CSV file (columns: Date, Category, Amount, Notes). Amounts should be numeric (Rs).")
223
  uploaded = st.file_uploader("Upload CSV", type=["csv"])
224
  if uploaded is not None:
225
  try:
 
229
  if not required.issubset(set(uploaded_df.columns)):
230
  st.error("CSV must include at least Date, Category, and Amount columns.")
231
  else:
232
+ # Normalize columns and types
233
+ if "Notes" not in uploaded_df.columns:
234
+ uploaded_df["Notes"] = ""
235
+ uploaded_df = uploaded_df[["Date", "Category", "Amount", "Notes"]]
236
+ uploaded_df["Amount"] = ensure_numeric_amount(uploaded_df["Amount"])
237
  uploaded_df["Date"] = pd.to_datetime(uploaded_df["Date"]).dt.date
238
  st.session_state.expenses = pd.concat([st.session_state.expenses, uploaded_df], ignore_index=True)
239
  st.success("Uploaded expenses added to your tracker.")