Update src/streamlit_app.py
Browse files- src/streamlit_app.py +97 -133
src/streamlit_app.py
CHANGED
|
@@ -3,177 +3,141 @@ import pandas as pd
|
|
| 3 |
|
| 4 |
# --- Page Config ---
|
| 5 |
st.set_page_config(
|
| 6 |
-
page_title="FitPulse Pro
|
| 7 |
page_icon="β‘",
|
| 8 |
-
layout="wide"
|
| 9 |
-
initial_sidebar_state="expanded",
|
| 10 |
)
|
| 11 |
|
| 12 |
-
# ---
|
| 13 |
st.markdown("""
|
| 14 |
<style>
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
}
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
background-color: #1E1E2F !important;
|
| 23 |
-
color: white;
|
| 24 |
-
}
|
| 25 |
-
|
| 26 |
-
/* Card Styling */
|
| 27 |
-
div.stButton > button:first-child {
|
| 28 |
-
background-color: #624BFF;
|
| 29 |
-
color: white;
|
| 30 |
-
border-radius: 8px;
|
| 31 |
-
border: none;
|
| 32 |
-
padding: 0.6rem 2rem;
|
| 33 |
-
transition: all 0.3s ease;
|
| 34 |
-
}
|
| 35 |
-
|
| 36 |
-
div.stButton > button:hover {
|
| 37 |
-
background-color: #4B39C1;
|
| 38 |
-
transform: translateY(-2px);
|
| 39 |
-
}
|
| 40 |
-
|
| 41 |
-
/* Custom Metric Box */
|
| 42 |
-
.metric-card {
|
| 43 |
-
background-color: white;
|
| 44 |
-
padding: 20px;
|
| 45 |
border-radius: 12px;
|
| 46 |
-
|
| 47 |
-
|
| 48 |
}
|
| 49 |
</style>
|
| 50 |
""", unsafe_allow_html=True)
|
| 51 |
|
| 52 |
-
# --- Session State
|
| 53 |
-
if '
|
| 54 |
-
st.session_state.
|
|
|
|
| 55 |
st.session_state.user_data = {}
|
| 56 |
|
| 57 |
-
# ---
|
| 58 |
def calculate_bmi(w, h_cm):
|
| 59 |
-
|
| 60 |
-
return round(w / (h_m ** 2), 2)
|
| 61 |
|
| 62 |
-
def
|
| 63 |
-
if bmi < 18.5: return "Underweight", "π΅", "
|
| 64 |
-
if bmi < 25: return "Normal", "π’", "
|
| 65 |
-
if bmi < 30: return "Overweight", "π ", "
|
| 66 |
-
return "Obese", "π΄", "
|
| 67 |
|
| 68 |
-
# --- Sidebar Navigation ---
|
| 69 |
with st.sidebar:
|
| 70 |
-
st.markdown("<h1 style='color: white;'>β‘ FitPulse
|
| 71 |
st.markdown("---")
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
| 77 |
st.markdown("---")
|
| 78 |
-
st.
|
|
|
|
|
|
|
|
|
|
| 79 |
|
| 80 |
-
# --- Page
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
st.
|
|
|
|
| 85 |
|
| 86 |
with st.container():
|
|
|
|
| 87 |
col1, col2 = st.columns(2)
|
|
|
|
| 88 |
with col1:
|
| 89 |
-
name = st.text_input("
|
| 90 |
-
height = st.number_input("Height (cm)", min_value=
|
|
|
|
| 91 |
with col2:
|
| 92 |
-
weight = st.number_input("Weight (kg)", min_value=
|
| 93 |
level = st.select_slider("Fitness Level", options=["Beginner", "Intermediate", "Advanced"])
|
| 94 |
-
|
| 95 |
-
goal = st.selectbox("
|
| 96 |
equip = st.multiselect("Available Equipment", ["Dumbbells", "Resistance Band", "Yoga Mat", "No Equipment", "Full Gym"], default=["No Equipment"])
|
| 97 |
|
| 98 |
-
if st.button("
|
| 99 |
if name and height > 0 and weight > 0:
|
| 100 |
st.session_state.user_data = {
|
| 101 |
"name": name, "height": height, "weight": weight,
|
| 102 |
"goal": goal, "level": level, "equip": equip,
|
| 103 |
"bmi": calculate_bmi(weight, height)
|
| 104 |
}
|
| 105 |
-
st.session_state.
|
| 106 |
-
st.success("
|
| 107 |
-
st.
|
| 108 |
else:
|
| 109 |
-
st.error("Please fill in all required fields
|
|
|
|
| 110 |
|
|
|
|
| 111 |
elif menu == "π Dashboard":
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
st.markdown("---")
|
| 129 |
-
|
| 130 |
-
# Dashboard Row 2: Visual Insights
|
| 131 |
-
c1, c2 = st.columns([2, 1])
|
| 132 |
-
with c1:
|
| 133 |
-
st.subheader("π Activity Overview")
|
| 134 |
-
st.info(f"**Recommended focus:** Based on your goal of **{ud['goal']}**, you should prioritize protein intake and consistent training.")
|
| 135 |
-
|
| 136 |
-
# Simulated Chart
|
| 137 |
-
chart_data = pd.DataFrame({
|
| 138 |
-
'Week': ['W1', 'W2', 'W3', 'W4'],
|
| 139 |
-
'Consistency': [70, 85, 80, 95]
|
| 140 |
-
})
|
| 141 |
-
st.line_chart(chart_data.set_index('Week'))
|
| 142 |
-
|
| 143 |
-
with c2:
|
| 144 |
-
st.subheader("π οΈ Setup")
|
| 145 |
-
st.write("**Equipment:**")
|
| 146 |
-
for item in ud['equip']:
|
| 147 |
-
st.write(f"β
{item}")
|
| 148 |
-
|
| 149 |
-
st.markdown(f"""
|
| 150 |
-
<div class="metric-card">
|
| 151 |
-
<h4>Health Advice</h4>
|
| 152 |
-
<p>{icon} {advice}</p>
|
| 153 |
-
</div>
|
| 154 |
-
""", unsafe_allow_html=True)
|
| 155 |
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
st.
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
# Detailed BMI Breakdown
|
| 164 |
-
st.subheader("BMI Analysis")
|
| 165 |
-
st.progress(min(ud['bmi']/40, 1.0))
|
| 166 |
-
st.write(f"Your BMI is **{ud['bmi']}**. Ideal range is 18.5 - 24.9.")
|
| 167 |
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
st.
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
st.
|
| 175 |
-
st.
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
# --- Page Config ---
|
| 5 |
st.set_page_config(
|
| 6 |
+
page_title="FitPulse Pro",
|
| 7 |
page_icon="β‘",
|
| 8 |
+
layout="wide"
|
|
|
|
| 9 |
)
|
| 10 |
|
| 11 |
+
# --- Modern UI Styling ---
|
| 12 |
st.markdown("""
|
| 13 |
<style>
|
| 14 |
+
.stApp { background-color: #F8FAFC; }
|
| 15 |
+
[data-testid="stSidebar"] { background-color: #111827; }
|
| 16 |
+
.main-card {
|
| 17 |
+
background: white;
|
| 18 |
+
padding: 2rem;
|
| 19 |
+
border-radius: 15px;
|
| 20 |
+
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
|
| 21 |
}
|
| 22 |
+
.metric-box {
|
| 23 |
+
background: #ffffff;
|
| 24 |
+
padding: 1.5rem;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
border-radius: 12px;
|
| 26 |
+
border: 1px solid #E2E8F0;
|
| 27 |
+
text-align: center;
|
| 28 |
}
|
| 29 |
</style>
|
| 30 |
""", unsafe_allow_html=True)
|
| 31 |
|
| 32 |
+
# --- Session State (The App's Memory) ---
|
| 33 |
+
if 'profile_complete' not in st.session_state:
|
| 34 |
+
st.session_state.profile_complete = False
|
| 35 |
+
if 'user_data' not in st.session_state:
|
| 36 |
st.session_state.user_data = {}
|
| 37 |
|
| 38 |
+
# --- Logic Functions ---
|
| 39 |
def calculate_bmi(w, h_cm):
|
| 40 |
+
return round(w / ((h_cm / 100) ** 2), 2)
|
|
|
|
| 41 |
|
| 42 |
+
def get_bmi_info(bmi):
|
| 43 |
+
if bmi < 18.5: return "Underweight", "π΅", "Needs Caloric Surplus"
|
| 44 |
+
if bmi < 25: return "Normal", "π’", "Maintain & Tone"
|
| 45 |
+
if bmi < 30: return "Overweight", "π ", "Caloric Deficit Advised"
|
| 46 |
+
return "Obese", "π΄", "Medical Consultation Advised"
|
| 47 |
|
| 48 |
+
# --- Sidebar Navigation Control ---
|
| 49 |
with st.sidebar:
|
| 50 |
+
st.markdown("<h1 style='color: white;'>β‘ FitPulse</h1>", unsafe_allow_html=True)
|
| 51 |
st.markdown("---")
|
| 52 |
+
|
| 53 |
+
# If profile isn't done, lock the navigation to Profile only
|
| 54 |
+
if not st.session_state.profile_complete:
|
| 55 |
+
menu = st.radio("GET STARTED", ["π€ Setup Profile"])
|
| 56 |
+
st.warning("Please complete setup to unlock Dashboard.")
|
| 57 |
+
else:
|
| 58 |
+
menu = st.radio("MENU", ["π Dashboard", "π Analytics", "βοΈ Edit Profile"])
|
| 59 |
+
|
| 60 |
st.markdown("---")
|
| 61 |
+
if st.session_state.profile_complete:
|
| 62 |
+
if st.button("Logout / Reset"):
|
| 63 |
+
st.session_state.profile_complete = False
|
| 64 |
+
st.rerun()
|
| 65 |
|
| 66 |
+
# --- Page Routing ---
|
| 67 |
|
| 68 |
+
# PAGE 1: SETUP PROFILE (Shown first)
|
| 69 |
+
if not st.session_state.profile_complete or menu == "βοΈ Edit Profile":
|
| 70 |
+
st.title("π€ Create Your Fitness Identity")
|
| 71 |
+
st.write("Enter your details to generate a custom fitness roadmap.")
|
| 72 |
|
| 73 |
with st.container():
|
| 74 |
+
st.markdown('<div class="main-card">', unsafe_allow_html=True)
|
| 75 |
col1, col2 = st.columns(2)
|
| 76 |
+
|
| 77 |
with col1:
|
| 78 |
+
name = st.text_input("Name", value=st.session_state.user_data.get('name', ""))
|
| 79 |
+
height = st.number_input("Height (cm)", min_value=50.0, max_value=250.0, value=float(st.session_state.user_data.get('height', 170.0)))
|
| 80 |
+
|
| 81 |
with col2:
|
| 82 |
+
weight = st.number_input("Weight (kg)", min_value=10.0, max_value=300.0, value=float(st.session_state.user_data.get('weight', 70.0)))
|
| 83 |
level = st.select_slider("Fitness Level", options=["Beginner", "Intermediate", "Advanced"])
|
| 84 |
+
|
| 85 |
+
goal = st.selectbox("Fitness Goal", ["Build Muscle", "Weight Loss", "Strength Gain", "Abs Building", "Flexible"])
|
| 86 |
equip = st.multiselect("Available Equipment", ["Dumbbells", "Resistance Band", "Yoga Mat", "No Equipment", "Full Gym"], default=["No Equipment"])
|
| 87 |
|
| 88 |
+
if st.button("π Generate My Dashboard"):
|
| 89 |
if name and height > 0 and weight > 0:
|
| 90 |
st.session_state.user_data = {
|
| 91 |
"name": name, "height": height, "weight": weight,
|
| 92 |
"goal": goal, "level": level, "equip": equip,
|
| 93 |
"bmi": calculate_bmi(weight, height)
|
| 94 |
}
|
| 95 |
+
st.session_state.profile_complete = True
|
| 96 |
+
st.success("Analysis Complete! Redirecting to Dashboard...")
|
| 97 |
+
st.rerun()
|
| 98 |
else:
|
| 99 |
+
st.error("Please fill in all required fields.")
|
| 100 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
| 101 |
|
| 102 |
+
# PAGE 2: DASHBOARD (Hidden until profile is done)
|
| 103 |
elif menu == "π Dashboard":
|
| 104 |
+
ud = st.session_state.user_data
|
| 105 |
+
status, icon, advice = get_bmi_info(ud['bmi'])
|
| 106 |
+
|
| 107 |
+
st.title(f"Welcome, {ud['name']} {icon}")
|
| 108 |
+
|
| 109 |
+
# Top Row Metrics
|
| 110 |
+
m1, m2, m3, m4 = st.columns(4)
|
| 111 |
+
with m1:
|
| 112 |
+
st.markdown(f'<div class="metric-box"><h5>BMI Score</h5><h2>{ud["bmi"]}</h2></div>', unsafe_allow_html=True)
|
| 113 |
+
with m2:
|
| 114 |
+
st.markdown(f'<div class="metric-box"><h5>Category</h5><h2 style="color:blue;">{status}</h2></div>', unsafe_allow_html=True)
|
| 115 |
+
with m3:
|
| 116 |
+
st.markdown(f'<div class="metric-box"><h5>Goal</h5><h2>{ud["goal"]}</h2></div>', unsafe_allow_html=True)
|
| 117 |
+
with m4:
|
| 118 |
+
st.markdown(f'<div class="metric-box"><h5>Level</h5><h2>{ud["level"]}</h2></div>', unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
|
| 120 |
+
st.markdown("### π― Your Action Plan")
|
| 121 |
+
c1, c2 = st.columns([2, 1])
|
| 122 |
+
|
| 123 |
+
with c1:
|
| 124 |
+
st.info(f"**Insight:** To reach your goal of **{ud['goal']}**, we've prepared a {ud['level']} track. You are using: {', '.join(ud['equip'])}.")
|
| 125 |
+
# Progress Chart
|
| 126 |
+
st.line_chart({"Progress": [10, 25, 45, 60, 85]})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
|
| 128 |
+
with c2:
|
| 129 |
+
st.success(f"**Advice:** {advice}")
|
| 130 |
+
st.warning("π
**Next Workout:** Tomorrow, 8:00 AM")
|
| 131 |
|
| 132 |
+
# PAGE 3: ANALYTICS
|
| 133 |
+
elif menu == "π Analytics":
|
| 134 |
+
st.title("π Health Deep Dive")
|
| 135 |
+
ud = st.session_state.user_data
|
| 136 |
+
st.write(f"Detailed breakdown for {ud['name']}")
|
| 137 |
+
|
| 138 |
+
# Metric explanation
|
| 139 |
+
st.write(f"Current Weight: **{ud['weight']} kg**")
|
| 140 |
+
st.write(f"Target Zone: **Normal (18.5 - 24.9)**")
|
| 141 |
+
|
| 142 |
+
delta = round(ud['bmi'] - 22, 2)
|
| 143 |
+
st.metric("Distance from 'Ideal' BMI (22.0)", delta, delta_color="inverse")
|