asif-coder commited on
Commit
8f615f2
Β·
verified Β·
1 Parent(s): 865b2a1

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +637 -0
app.py ADDED
@@ -0,0 +1,637 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ import streamlit as st
3
+ import json
4
+ import os
5
+ from datetime import datetime, timedelta
6
+ import pandas as pd
7
+ import plotly.express as px
8
+ import plotly.graph_objects as go
9
+ from collections import defaultdict
10
+ import base64
11
+
12
+ # Page configuration
13
+ st.set_page_config(
14
+ page_title="Atomic Habits Tracker Pro",
15
+ page_icon="πŸ”₯",
16
+ layout="wide",
17
+ initial_sidebar_state="expanded"
18
+ )
19
+
20
+ # Categories with emojis
21
+ CATEGORIES = {
22
+ "Health": "πŸ’ͺ",
23
+ "Learning": "πŸ“š",
24
+ "Mindfulness": "🧘",
25
+ "Productivity": "⚑",
26
+ "Creativity": "🎨",
27
+ "Social": "πŸ‘₯",
28
+ "Finance": "πŸ’°",
29
+ "Other": "πŸ”Ή"
30
+ }
31
+
32
+ # Initialize session state for theme
33
+ if 'dark_mode' not in st.session_state:
34
+ st.session_state.dark_mode = False
35
+
36
+ # Theme toggle function
37
+ def toggle_theme():
38
+ st.session_state.dark_mode = not st.session_state.dark_mode
39
+
40
+ # Dynamic CSS based on theme
41
+ def get_css():
42
+ if st.session_state.dark_mode:
43
+ return """
44
+ <style>
45
+ .main-header {
46
+ font-size: 3rem;
47
+ font-weight: bold;
48
+ background: linear-gradient(90deg, #FF6B6B, #4ECDC4);
49
+ -webkit-background-clip: text;
50
+ -webkit-text-fill-color: transparent;
51
+ text-align: center;
52
+ margin-bottom: 2rem;
53
+ }
54
+ .stApp {
55
+ background-color: #0e1117;
56
+ color: #fafafa;
57
+ }
58
+ .quote-box {
59
+ background: #1e1e1e;
60
+ border-left: 5px solid #4ECDC4;
61
+ padding: 15px;
62
+ margin: 20px 0;
63
+ font-style: italic;
64
+ color: #fafafa;
65
+ border-radius: 0 10px 10px 0;
66
+ }
67
+ .habit-card {
68
+ background: #1e1e1e;
69
+ padding: 20px;
70
+ border-radius: 15px;
71
+ margin: 10px 0;
72
+ box-shadow: 0 4px 6px rgba(0,0,0,0.3);
73
+ border: 1px solid #333;
74
+ }
75
+ .streak-badge {
76
+ background: #FFD93D;
77
+ color: #333;
78
+ padding: 5px 15px;
79
+ border-radius: 20px;
80
+ font-weight: bold;
81
+ display: inline-block;
82
+ margin: 5px;
83
+ }
84
+ .category-badge {
85
+ background: #333;
86
+ color: #fff;
87
+ padding: 3px 10px;
88
+ border-radius: 15px;
89
+ font-size: 0.8rem;
90
+ display: inline-block;
91
+ margin: 5px 0;
92
+ }
93
+ .stButton>button {
94
+ border-radius: 25px;
95
+ transition: all 0.3s;
96
+ }
97
+ .stTextInput>div>div>input, .stSelectbox>div>div>select {
98
+ background-color: #1e1e1e;
99
+ color: #fafafa;
100
+ border-radius: 10px;
101
+ }
102
+ .stProgress > div > div > div > div {
103
+ background-color: #4ECDC4;
104
+ }
105
+ div[data-testid="stSidebar"] {
106
+ background-color: #1e1e1e;
107
+ }
108
+ h1, h2, h3, h4, h5, h6, p, label {
109
+ color: #fafafa !important;
110
+ }
111
+ .footer-text {
112
+ color: #888 !important;
113
+ }
114
+ </style>
115
+ """
116
+ else:
117
+ return """
118
+ <style>
119
+ .main-header {
120
+ font-size: 3rem;
121
+ font-weight: bold;
122
+ background: linear-gradient(90deg, #FF6B6B, #4ECDC4);
123
+ -webkit-background-clip: text;
124
+ -webkit-text-fill-color: transparent;
125
+ text-align: center;
126
+ margin-bottom: 2rem;
127
+ }
128
+ .quote-box {
129
+ background: #f0f2f6;
130
+ border-left: 5px solid #4ECDC4;
131
+ padding: 15px;
132
+ margin: 20px 0;
133
+ font-style: italic;
134
+ border-radius: 0 10px 10px 0;
135
+ }
136
+ .habit-card {
137
+ background: white;
138
+ padding: 20px;
139
+ border-radius: 15px;
140
+ margin: 10px 0;
141
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
142
+ }
143
+ .streak-badge {
144
+ background: #FFD93D;
145
+ color: #333;
146
+ padding: 5px 15px;
147
+ border-radius: 20px;
148
+ font-weight: bold;
149
+ display: inline-block;
150
+ margin: 5px;
151
+ }
152
+ .category-badge {
153
+ background: #e0e0e0;
154
+ color: #333;
155
+ padding: 3px 10px;
156
+ border-radius: 15px;
157
+ font-size: 0.8rem;
158
+ display: inline-block;
159
+ margin: 5px 0;
160
+ }
161
+ .stButton>button {
162
+ border-radius: 25px;
163
+ transition: all 0.3s;
164
+ }
165
+ .stTextInput>div>div>input, .stSelectbox>div>div>select {
166
+ border-radius: 10px;
167
+ }
168
+ .stProgress > div > div > div > div {
169
+ background-color: #4ECDC4;
170
+ }
171
+ .footer-text {
172
+ color: #666 !important;
173
+ }
174
+ </style>
175
+ """
176
+
177
+ st.markdown(get_css(), unsafe_allow_html=True)
178
+
179
+ # Data persistence
180
+ DATA_FILE = "habits_data.json"
181
+
182
+ def load_data():
183
+ """Load habits data from file"""
184
+ if os.path.exists(DATA_FILE):
185
+ with open(DATA_FILE, 'r') as f:
186
+ data = json.load(f)
187
+ # Migrate old data to include categories if missing
188
+ for habit in data.get("habits", []):
189
+ if "category" not in habit:
190
+ habit["category"] = "Other"
191
+ if "reminder_time" not in habit:
192
+ habit["reminder_time"] = "09:00"
193
+ return data
194
+ return {"habits": [], "completions": {}, "reminders_sent": {}}
195
+
196
+ def save_data(data):
197
+ """Save habits data to file"""
198
+ with open(DATA_FILE, 'w') as f:
199
+ json.dump(data, f, indent=2)
200
+
201
+ def get_streak(habit_id, completions, habit_data):
202
+ """Calculate current streak for a habit"""
203
+ if habit_id not in completions or not completions[habit_id]:
204
+ return 0
205
+
206
+ dates = sorted([datetime.strptime(d, "%Y-%m-%d").date() for d in completions[habit_id]], reverse=True)
207
+
208
+ if not dates:
209
+ return 0
210
+
211
+ today = datetime.now().date()
212
+ streak = 0
213
+ check_date = today
214
+
215
+ if dates[0] == today or dates[0] == today - timedelta(days=1):
216
+ for i, date in enumerate(dates):
217
+ expected_date = today - timedelta(days=i)
218
+ if date == expected_date or (i == 0 and date == today - timedelta(days=1)):
219
+ streak += 1
220
+ else:
221
+ break
222
+ else:
223
+ streak = 0
224
+
225
+ return streak
226
+
227
+ def get_weekly_completion(habit_id, completions):
228
+ """Get completion rate for last 7 days"""
229
+ if habit_id not in completions:
230
+ return 0
231
+
232
+ today = datetime.now().date()
233
+ week_ago = today - timedelta(days=7)
234
+
235
+ week_completions = sum(
236
+ 1 for d in completions[habit_id]
237
+ if week_ago <= datetime.strptime(d, "%Y-%m-%d").date() <= today
238
+ )
239
+
240
+ return (week_completions / 7) * 100
241
+
242
+ def get_monthly_stats(habit_id, completions):
243
+ """Get monthly completion stats"""
244
+ if habit_id not in completions:
245
+ return 0, 0
246
+
247
+ today = datetime.now().date()
248
+ month_ago = today - timedelta(days=30)
249
+
250
+ month_completions = [
251
+ datetime.strptime(d, "%Y-%m-%d").date()
252
+ for d in completions[habit_id]
253
+ if month_ago <= datetime.strptime(d, "%Y-%m-%d").date() <= today
254
+ ]
255
+
256
+ return len(month_completions), (len(month_completions) / 30) * 100
257
+
258
+ def export_to_csv():
259
+ """Export all habit data to CSV"""
260
+ data = st.session_state.data
261
+ rows = []
262
+
263
+ for habit in data["habits"]:
264
+ habit_id = habit["id"]
265
+ completions = data["completions"].get(habit_id, [])
266
+
267
+ for date in completions:
268
+ rows.append({
269
+ "Habit Name": habit["name"],
270
+ "Category": habit["category"],
271
+ "Emoji": habit["emoji"],
272
+ "Completion Date": date,
273
+ "Created At": habit["created_at"]
274
+ })
275
+
276
+ if not rows:
277
+ # Create summary even if no completions
278
+ for habit in data["habits"]:
279
+ rows.append({
280
+ "Habit Name": habit["name"],
281
+ "Category": habit["category"],
282
+ "Emoji": habit["emoji"],
283
+ "Completion Date": "No completions yet",
284
+ "Created At": habit["created_at"]
285
+ })
286
+
287
+ df = pd.DataFrame(rows)
288
+ return df.to_csv(index=False).encode('utf-8')
289
+
290
+ def check_reminders():
291
+ """Check and display due reminders"""
292
+ current_time = datetime.now().strftime("%H:%M")
293
+ today = datetime.now().strftime("%Y-%m-%d")
294
+
295
+ reminders_due = []
296
+
297
+ for habit in st.session_state.data["habits"]:
298
+ reminder_time = habit.get("reminder_time", "09:00")
299
+ habit_id = habit["id"]
300
+
301
+ # Check if reminder time matches current time (within 1 minute window)
302
+ if reminder_time == current_time:
303
+ # Check if not already completed today and not already reminded
304
+ completions = st.session_state.data["completions"].get(habit_id, [])
305
+ reminders_sent = st.session_state.data.get("reminders_sent", {})
306
+
307
+ if today not in completions and reminders_sent.get(habit_id) != today:
308
+ reminders_due.append(habit)
309
+ st.session_state.data["reminders_sent"][habit_id] = today
310
+ save_data(st.session_state.data)
311
+
312
+ return reminders_due
313
+
314
+ # Initialize session state
315
+ if 'data' not in st.session_state:
316
+ st.session_state.data = load_data()
317
+
318
+ # Check for reminders
319
+ reminders = check_reminders()
320
+
321
+ # Sidebar
322
+ with st.sidebar:
323
+ # Dark mode toggle at top
324
+ st.button("πŸŒ™ Dark Mode" if not st.session_state.dark_mode else "β˜€οΈ Light Mode",
325
+ on_click=toggle_theme, use_container_width=True)
326
+
327
+ st.markdown("---")
328
+
329
+ st.header("βž• Add New Habit")
330
+ st.markdown("*Make it obvious, make it easy*")
331
+
332
+ with st.form("new_habit"):
333
+ habit_name = st.text_input("Habit Name", placeholder="e.g., Read 1 page")
334
+ habit_category = st.selectbox("Category", list(CATEGORIES.keys()),
335
+ format_func=lambda x: f"{CATEGORIES[x]} {x}")
336
+ habit_color = st.color_picker("Choose Color", "#4ECDC4")
337
+ habit_emoji = st.selectbox("Icon", ["πŸ“š", "πŸƒ", "πŸ’§", "🧘", "πŸ’ͺ", "🎸", "πŸ’»", "🌱", "✍️", "🎯", "πŸ₯—", "πŸ’€"])
338
+ reminder_time = st.time_input("⏰ Daily Reminder", value=datetime.strptime("09:00", "%H:%M").time())
339
+
340
+ submitted = st.form_submit_button("Create Habit", use_container_width=True)
341
+
342
+ if submitted and habit_name:
343
+ habit_id = f"habit_{datetime.now().strftime('%Y%m%d%H%M%S')}"
344
+ new_habit = {
345
+ "id": habit_id,
346
+ "name": habit_name,
347
+ "category": habit_category,
348
+ "color": habit_color,
349
+ "emoji": habit_emoji,
350
+ "reminder_time": reminder_time.strftime("%H:%M"),
351
+ "created_at": datetime.now().strftime("%Y-%m-%d")
352
+ }
353
+ st.session_state.data["habits"].append(new_habit)
354
+ st.session_state.data["completions"][habit_id] = []
355
+ save_data(st.session_state.data)
356
+ st.success(f"Created: {habit_emoji} {habit_name}")
357
+ st.rerun()
358
+
359
+ st.markdown("---")
360
+
361
+ # Export Section
362
+ st.header("πŸ“₯ Export Data")
363
+ if st.session_state.data["habits"]:
364
+ csv = export_to_csv()
365
+ st.download_button(
366
+ label="Download CSV",
367
+ data=csv,
368
+ file_name=f"habits_export_{datetime.now().strftime('%Y%m%d')}.csv",
369
+ mime="text/csv",
370
+ use_container_width=True
371
+ )
372
+ st.caption("Export all your habit data for backup or analysis")
373
+ else:
374
+ st.info("Add habits to enable export")
375
+
376
+ st.markdown("---")
377
+
378
+ # Stats Summary
379
+ if st.session_state.data["habits"]:
380
+ st.header("πŸ“Š Quick Stats")
381
+ total_habits = len(st.session_state.data["habits"])
382
+ total_completions = sum(len(v) for v in st.session_state.data["completions"].values())
383
+ active_streaks = sum(
384
+ 1 for h in st.session_state.data["habits"]
385
+ if get_streak(h["id"], st.session_state.data["completions"], h) > 0
386
+ )
387
+
388
+ st.metric("Total Habits", total_habits)
389
+ st.metric("Total Completions", total_completions)
390
+ st.metric("Active Streaks", active_streaks)
391
+
392
+ # Display reminders
393
+ if reminders:
394
+ st.markdown("---")
395
+ st.subheader("πŸ”” Reminders")
396
+ for habit in reminders:
397
+ st.info(f"⏰ Time to complete: {habit['emoji']} **{habit['name']}**!")
398
+
399
+ # Main Header
400
+ st.markdown('<h1 class="main-header">πŸ”₯ Atomic Habits Tracker Pro</h1>', unsafe_allow_html=True)
401
+
402
+ # Daily Quote
403
+ quotes = [
404
+ "You do not rise to the level of your goals. You fall to the level of your systems.",
405
+ "Every action you take is a vote for the type of person you wish to become.",
406
+ "Success is the product of daily habitsβ€”not once-in-a-lifetime transformations.",
407
+ "The cost of your good habit is in the dozens of moments before you do it.",
408
+ "You should be far more concerned with your current trajectory than with your current results.",
409
+ "Habits are the compound interest of self-improvement.",
410
+ "You don't have to be the victim of your environment. You can also be the architect of it."
411
+ ]
412
+ st.markdown(f'<div class="quote-box">"{quotes[datetime.now().day % len(quotes)]}" β€” James Clear</div>', unsafe_allow_html=True)
413
+
414
+ # Category Filter
415
+ if st.session_state.data["habits"]:
416
+ all_categories = ["All"] + list(CATEGORIES.keys())
417
+ selected_category = st.selectbox("Filter by Category", all_categories, horizontal=True)
418
+
419
+ filtered_habits = st.session_state.data["habits"]
420
+ if selected_category != "All":
421
+ filtered_habits = [h for h in filtered_habits if h["category"] == selected_category]
422
+
423
+ # Main Content
424
+ if not st.session_state.data["habits"]:
425
+ st.info("πŸ‘ˆ Start by adding your first habit in the sidebar! Remember: start small (2-minute rule).")
426
+ else:
427
+ today = datetime.now().strftime("%Y-%m-%d")
428
+ today_display = datetime.now().strftime("%A, %B %d, %Y")
429
+
430
+ st.subheader(f"πŸ“… {today_display}")
431
+
432
+ # Group by category if showing all
433
+ if selected_category == "All":
434
+ for category in CATEGORIES.keys():
435
+ category_habits = [h for h in filtered_habits if h["category"] == category]
436
+ if category_habits:
437
+ st.markdown(f"### {CATEGORIES[category]} {category}")
438
+ cols = st.columns(2)
439
+ for idx, habit in enumerate(category_habits):
440
+ with cols[idx % 2]:
441
+ render_habit_card(habit, today)
442
+ else:
443
+ cols = st.columns(2)
444
+ for idx, habit in enumerate(filtered_habits):
445
+ with cols[idx % 2]:
446
+ render_habit_card(habit, today)
447
+
448
+ def render_habit_card(habit, today):
449
+ """Render individual habit card"""
450
+ habit_id = habit["id"]
451
+ completions = st.session_state.data["completions"].get(habit_id, [])
452
+ is_completed_today = today in completions
453
+ streak = get_streak(habit_id, st.session_state.data["completions"], habit)
454
+ weekly_rate = get_weekly_completion(habit_id, st.session_state.data["completions"])
455
+ monthly_count, monthly_rate = get_monthly_stats(habit_id, st.session_state.data["completions"])
456
+
457
+ with st.container():
458
+ st.markdown(f"""
459
+ <div class="habit-card" style="border-left: 5px solid {habit['color']};">
460
+ """, unsafe_allow_html=True)
461
+
462
+ # Header with category badge
463
+ col1, col2 = st.columns([3, 1])
464
+
465
+ with col1:
466
+ st.markdown(f"### {habit['emoji']} {habit['name']}")
467
+ st.markdown(f'<span class="category-badge">{CATEGORIES[habit["category"]]} {habit["category"]}</span>', unsafe_allow_html=True)
468
+ st.caption(f"⏰ Reminder at {habit.get('reminder_time', '09:00')}")
469
+
470
+ with col2:
471
+ if is_completed_today:
472
+ st.success("βœ… Done!")
473
+ if st.button("Undo", key=f"undo_{habit_id}", help="Mark as not done"):
474
+ st.session_state.data["completions"][habit_id].remove(today)
475
+ save_data(st.session_state.data)
476
+ st.rerun()
477
+ else:
478
+ if st.button("Complete βœ“", key=f"complete_{habit_id}", type="primary", use_container_width=True):
479
+ st.session_state.data["completions"][habit_id].append(today)
480
+ save_data(st.session_state.data)
481
+ st.balloons()
482
+ st.rerun()
483
+
484
+ # Stats row
485
+ stat_col1, stat_col2, stat_col3 = st.columns(3)
486
+
487
+ with stat_col1:
488
+ if streak > 0:
489
+ st.markdown(f'<span class="streak-badge">πŸ”₯ {streak} day streak</span>', unsafe_allow_html=True)
490
+ else:
491
+ st.markdown("πŸ”Ή Start today!")
492
+
493
+ with stat_col2:
494
+ st.progress(weekly_rate / 100, text=f"Week: {weekly_rate:.0f}%")
495
+
496
+ with stat_col3:
497
+ st.caption(f"30d: {monthly_count} days ({monthly_rate:.0f}%)")
498
+
499
+ # Edit/Delete
500
+ with st.expander("βš™οΈ Settings"):
501
+ edit_col1, edit_col2 = st.columns(2)
502
+
503
+ with edit_col1:
504
+ new_time = st.time_input("Update reminder",
505
+ value=datetime.strptime(habit.get("reminder_time", "09:00"), "%H:%M").time(),
506
+ key=f"time_{habit_id}")
507
+ if new_time.strftime("%H:%M") != habit.get("reminder_time", "09:00"):
508
+ habit["reminder_time"] = new_time.strftime("%H:%M")
509
+ save_data(st.session_state.data)
510
+ st.success("Updated!")
511
+
512
+ with edit_col2:
513
+ if st.button("πŸ—‘οΈ Delete Habit", key=f"del_{habit_id}", type="secondary"):
514
+ st.session_state.data["habits"].remove(habit)
515
+ del st.session_state.data["completions"][habit_id]
516
+ save_data(st.session_state.data)
517
+ st.rerun()
518
+
519
+ st.markdown("</div>", unsafe_allow_html=True)
520
+
521
+ # Analytics Section
522
+ st.markdown("---")
523
+ st.subheader("πŸ“Š Analytics Dashboard")
524
+
525
+ if st.session_state.data["habits"]:
526
+ # Category breakdown
527
+ cat_data = {}
528
+ for habit in st.session_state.data["habits"]:
529
+ cat = habit["category"]
530
+ completions = len(st.session_state.data["completions"].get(habit["id"], []))
531
+ cat_data[cat] = cat_data.get(cat, 0) + completions
532
+
533
+ fig_pie = go.Figure(data=[go.Pie(
534
+ labels=[f"{CATEGORIES[k]} {k}" for k in cat_data.keys()],
535
+ values=list(cat_data.values()),
536
+ hole=.4,
537
+ marker_colors=px.colors.qualitative.Set3
538
+ )])
539
+ fig_pie.update_layout(title_text="Completions by Category", showlegend=True)
540
+
541
+ # Timeline data
542
+ timeline_data = defaultdict(int)
543
+ for habit_id, dates in st.session_state.data["completions"].items():
544
+ for date in dates:
545
+ timeline_data[date] += 1
546
+
547
+ if timeline_data:
548
+ df_timeline = pd.DataFrame([
549
+ {"Date": datetime.strptime(d, "%Y-%m-%d"), "Total Completions": c}
550
+ for d, c in sorted(timeline_data.items())
551
+ ])
552
+
553
+ fig_timeline = px.line(df_timeline, x="Date", y="Total Completions",
554
+ title="Your Consistency Over Time",
555
+ markers=True)
556
+ fig_timeline.update_layout(xaxis_title="Date", yaxis_title="Habits Completed")
557
+ else:
558
+ fig_timeline = go.Figure()
559
+ fig_timeline.update_layout(title="Start completing habits to see your timeline!")
560
+
561
+ col1, col2 = st.columns(2)
562
+ with col1:
563
+ st.plotly_chart(fig_pie, use_container_width=True)
564
+ with col2:
565
+ st.plotly_chart(fig_timeline, use_container_width=True)
566
+
567
+ # Detailed table
568
+ st.subheader("πŸ“‹ Habit Performance Table")
569
+ table_data = []
570
+ for habit in st.session_state.data["habits"]:
571
+ habit_id = habit["id"]
572
+ completions = st.session_state.data["completions"].get(habit_id, [])
573
+ streak = get_streak(habit_id, st.session_state.data["completions"], habit)
574
+ total = len(completions)
575
+
576
+ table_data.append({
577
+ "Habit": f"{habit['emoji']} {habit['name']}",
578
+ "Category": f"{CATEGORIES[habit['category']]} {habit['category']}",
579
+ "Current Streak": f"πŸ”₯ {streak}" if streak > 0 else "0",
580
+ "Total Days": total,
581
+ "Success Rate": f"{(total / max(1, (datetime.now() - datetime.strptime(habit['created_at'], '%Y-%m-%d')).days)) * 100:.1f}%"
582
+ })
583
+
584
+ df_table = pd.DataFrame(table_data)
585
+ st.dataframe(df_table, use_container_width=True, hide_index=True)
586
+
587
+ # Calendar heatmap for selected habit
588
+ st.subheader("πŸ“… Detailed Activity View")
589
+ selected_habit = st.selectbox(
590
+ "Select habit to view calendar",
591
+ options=[h["name"] for h in st.session_state.data["habits"]],
592
+ format_func=lambda x: next(f"{h['emoji']} {h['name']} ({h['category']})" for h in st.session_state.data["habits"] if h["name"] == x)
593
+ )
594
+
595
+ selected_habit_data = next(h for h in st.session_state.data["habits"] if h["name"] == selected_habit)
596
+ completions = st.session_state.data["completions"].get(selected_habit_data["id"], [])
597
+
598
+ if completions:
599
+ # Create comprehensive calendar view
600
+ dates_data = []
601
+ for i in range(90): # Last 90 days
602
+ date = datetime.now().date() - timedelta(days=i)
603
+ date_str = date.strftime("%Y-%m-%d")
604
+ completed = date_str in completions
605
+ dates_data.append({
606
+ "Date": date,
607
+ "Completed": "Yes" if completed else "No",
608
+ "Week": date.isocalendar()[1],
609
+ "Day": date.strftime("%a"),
610
+ "Month": date.strftime("%b")
611
+ })
612
+
613
+ cal_df = pd.DataFrame(dates_data)
614
+
615
+ # Heatmap
616
+ fig_cal = px.density_heatmap(
617
+ cal_df,
618
+ x="Week",
619
+ y="Day",
620
+ z=[1 if c == "Yes" else 0 for c in cal_df["Completed"]],
621
+ title=f"Last 90 Days Activity - {selected_habit}",
622
+ color_continuous_scale=[(0, "#e0e0e0"), (1, selected_habit_data["color"])],
623
+ labels={"color": "Completed"}
624
+ )
625
+ st.plotly_chart(fig_cal, use_container_width=True)
626
+ else:
627
+ st.info("No data yet. Complete your habit today to start tracking!")
628
+
629
+ # Footer
630
+ st.markdown("---")
631
+ st.markdown(f"""
632
+ <div style="text-align: center; padding: 20px;">
633
+ <p class="footer-text">Built with ❀️ using Streamlit | Deployed on Hugging Face Spaces</p>
634
+ <p class="footer-text" style="font-size: 0.8rem;">"Small changes, remarkable results" β€” James Clear</p>
635
+ <p class="footer-text" style="font-size: 0.7rem;">Current theme: {"Dark" if st.session_state.dark_mode else "Light"} Mode</p>
636
+ </div>
637
+ """, unsafe_allow_html=True)