arif670 commited on
Commit
949751a
ยท
verified ยท
1 Parent(s): fc7c4d7

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +470 -0
app.py ADDED
@@ -0,0 +1,470 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import firebase_admin
3
+ from firebase_admin import credentials, firestore
4
+ import pandas as pd
5
+
6
+ # Set Page Configuration (must be the first Streamlit command)
7
+ st.set_page_config(
8
+ page_title="ERP Admin Panel",
9
+ page_icon="๐Ÿ› ๏ธ",
10
+ layout="wide",
11
+ initial_sidebar_state="expanded"
12
+ )
13
+
14
+ # Initialize Firebase
15
+ try:
16
+ cred = credentials.Certificate("serviceAccountKey.json")
17
+ firebase_admin.initialize_app(cred)
18
+ except ValueError:
19
+ # If the app is already initialized, skip re-initialization
20
+ pass
21
+
22
+ db = firestore.client()
23
+
24
+ # Default User Authentication
25
+ DEFAULT_USERS = {
26
+ "Admin": {"password": "Admin", "role": "Admin"},
27
+ "Manager": {"password": "Manager", "role": "Manager"},
28
+ }
29
+
30
+ def normalize_email(email):
31
+ """Normalize email by converting it to lowercase."""
32
+ return email.strip().lower()
33
+
34
+ def initialize_default_users():
35
+ """
36
+ Initialize default users in Firestore if they don't already exist.
37
+ """
38
+ for email, user_data in DEFAULT_USERS.items():
39
+ normalized_email = normalize_email(email)
40
+ emp_doc = db.collection("Employees").document(normalized_email).get()
41
+ if not emp_doc.exists:
42
+ with st.spinner(f"โณ Initializing default user: {email}"):
43
+ db.collection("Employees").document(normalized_email).set({
44
+ "name": email.capitalize(), # Use email as name for simplicity
45
+ "email": normalized_email,
46
+ "phone": "N/A", # Default placeholder
47
+ "designation": "Default User",
48
+ "role": user_data["role"],
49
+ "password": user_data["password"],
50
+ "force_password_change": False # No need to change default passwords
51
+ })
52
+ st.success(f"โœ… Default user '{email}' initialized successfully.")
53
+
54
+ def login_user(email, password):
55
+ # Normalize email
56
+ email = normalize_email(email)
57
+
58
+ # Check if the user is a default user (Admin/Manager)
59
+ if email in DEFAULT_USERS and DEFAULT_USERS[email]["password"] == password:
60
+ return DEFAULT_USERS[email]["role"]
61
+
62
+ # Check if the user exists in Firestore (employees)
63
+ emp_doc = db.collection("Employees").document(email).get()
64
+ if emp_doc.exists:
65
+ emp_data = emp_doc.to_dict()
66
+ if "password" in emp_data and emp_data["password"] == password:
67
+ return emp_data.get("role", "Employee") # Default role is "Employee"
68
+
69
+ # If no match is found, return None
70
+ st.error("โŒ Invalid credentials. Please try again.")
71
+ return None
72
+
73
+ # Sidebar Navigation with Icons
74
+ st.sidebar.markdown("""
75
+ <style>
76
+ .sidebar .sidebar-content {
77
+ background-color: #e6f7ff; /* Light Blue Background */
78
+ border-radius: 15px;
79
+ box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.2);
80
+ }
81
+ .sidebar .stButton button {
82
+ background-color: #007bff;
83
+ color: white;
84
+ border-radius: 10px;
85
+ padding: 10px;
86
+ font-size: 16px;
87
+ box-shadow: 3px 3px 8px rgba(0, 0, 0, 0.3);
88
+ }
89
+ .sidebar .stButton button:hover {
90
+ background-color: #0056b3;
91
+ transform: scale(1.05);
92
+ transition: all 0.3s ease;
93
+ }
94
+ </style>
95
+ """, unsafe_allow_html=True)
96
+
97
+ st.sidebar.title("๐Ÿš€ Admin Panel ๐Ÿ› ๏ธ")
98
+
99
+ # Initialize Default Users
100
+ if "default_users_initialized" not in st.session_state:
101
+ initialize_default_users()
102
+ st.session_state.default_users_initialized = True
103
+
104
+ # Login Section
105
+ if "logged_in" not in st.session_state:
106
+ st.session_state.logged_in = False
107
+ st.session_state.user_role = None
108
+
109
+ if not st.session_state.logged_in:
110
+ st.sidebar.subheader("๐Ÿ”‘ Login")
111
+ email = st.sidebar.text_input("๐Ÿ“ง Email", placeholder="Enter your email")
112
+ password = st.sidebar.text_input("๐Ÿ”’ Password", type="password", placeholder="Enter your password")
113
+
114
+ if st.sidebar.button("๐Ÿ”“ Login"):
115
+ user_role = login_user(email, password)
116
+ if user_role:
117
+ # Check if the user needs to change their password
118
+ emp_doc = db.collection("Employees").document(normalize_email(email)).get()
119
+ if emp_doc.exists:
120
+ emp_data = emp_doc.to_dict()
121
+ if emp_data.get("force_password_change", False):
122
+ st.session_state.force_password_change = True
123
+ st.session_state.temp_email = normalize_email(email)
124
+ st.experimental_rerun()
125
+
126
+ # Update session state for successful login
127
+ st.session_state.logged_in = True
128
+ st.session_state.user_role = user_role
129
+ st.experimental_rerun()
130
+ else:
131
+ if "force_password_change" in st.session_state and st.session_state.force_password_change:
132
+ st.header("โš ๏ธ Change Password")
133
+ st.warning("๐Ÿ”’ You must change your password before proceeding.")
134
+ new_password = st.text_input("๐Ÿ” New Password", type="password", placeholder="Enter a new password")
135
+ confirm_password = st.text_input("๐Ÿ”’ Confirm Password", type="password", placeholder="Confirm your new password")
136
+
137
+ if st.button("โœ… Save Password"):
138
+ if not new_password or not confirm_password:
139
+ st.error("โŒ Please fill in both fields.")
140
+ elif new_password != confirm_password:
141
+ st.error("โŒ Passwords do not match.")
142
+ elif len(new_password) < 8 or not any(char.isdigit() for char in new_password) or not any(char.isupper() for char in new_password):
143
+ st.error("โŒ Password must be at least 8 characters long, include a number, and an uppercase letter.")
144
+ else:
145
+ # Update password in Firestore
146
+ db.collection("Employees").document(st.session_state.temp_email).update({
147
+ "password": new_password,
148
+ "force_password_change": False
149
+ })
150
+ st.success("โœ… Password changed successfully!")
151
+ st.session_state.force_password_change = False
152
+ st.session_state.logged_in = True
153
+ st.experimental_rerun()
154
+ else:
155
+ st.sidebar.subheader(f"๐Ÿ‘‹ Welcome, {st.session_state.user_role}")
156
+ menu = {
157
+ "๐Ÿ‘ฅ Employee Management": "employee",
158
+ "๐Ÿ›ก๏ธ Role Management": "role",
159
+ "๐Ÿ”’ Access Control": "access",
160
+ "๐Ÿ“Š Dashboards": "dashboard",
161
+ "๐Ÿ“‹ Reporting Templates": "report",
162
+ "๐Ÿค– Chatbot": "chatbot",
163
+ "๐Ÿง  Advanced Analytics": "analytics",
164
+ }
165
+ choice = st.sidebar.radio("๐Ÿ“š Menu", list(menu.keys()))
166
+
167
+ # Logout Button
168
+ if st.sidebar.button("๐Ÿšช Logout"):
169
+ st.session_state.logged_in = False
170
+ st.session_state.user_role = None
171
+ st.experimental_rerun()
172
+
173
+ # Theme Selection
174
+ st.sidebar.subheader("๐ŸŽจ Appearance")
175
+ theme = st.sidebar.selectbox("๐ŸŒ™ Choose Theme", ["โ˜€๏ธ Light", "๐ŸŒ™ Dark"])
176
+ if theme == "โ˜€๏ธ Light":
177
+ st.markdown("""
178
+ <style>
179
+ .stApp {background-color: #e6f7ff; color: black;} /* Light Blue Background */
180
+ </style>
181
+ """, unsafe_allow_html=True)
182
+ elif theme == "๐ŸŒ™ Dark":
183
+ st.markdown("""
184
+ <style>
185
+ .stApp {background-color: #1E1E1E; color: white;}
186
+ </style>
187
+ """, unsafe_allow_html=True)
188
+
189
+ # Main Content
190
+ if "logged_in" in st.session_state and st.session_state.logged_in:
191
+ # Role-Based Access Control
192
+ user_role = st.session_state.user_role
193
+ if user_role not in ["Admin", "Manager", "Engineer", "Accountant"]:
194
+ st.error("โŒ Unauthorized access. Please contact the administrator.")
195
+ st.stop()
196
+
197
+ if menu[choice] == "employee":
198
+ if user_role not in ["Admin", "Manager"]:
199
+ st.error("โŒ You do not have permission to access this module.")
200
+ st.stop()
201
+
202
+ st.header("๐Ÿ‘ฅ Employee Management")
203
+
204
+ # Real-Time Updates for Employees
205
+ def fetch_employees():
206
+ employees_ref = db.collection("Employees").stream()
207
+ return [{"ID": emp.id, "Name": emp.to_dict()["name"], "Email": emp.to_dict()["email"], "Role": emp.to_dict()["role"]} for emp in employees_ref]
208
+
209
+ # Add Employee Form
210
+ with st.form("add_employee_form"):
211
+ st.subheader("๐Ÿ“ Add Employee")
212
+ col1, col2 = st.columns(2)
213
+ with col1:
214
+ name = st.text_input("๐Ÿ‘ค Name", placeholder="Enter full name", help="Full name of the employee")
215
+ email = st.text_input("๐Ÿ“ง Email", placeholder="Enter email address", help="Official email address")
216
+ with col2:
217
+ phone = st.text_input("๐Ÿ“ž Phone", placeholder="Enter phone number", help="Contact number")
218
+ designation = st.text_input("๐Ÿ’ผ Designation", placeholder="Enter job title", help="Job role or position")
219
+
220
+ role = st.selectbox("๐Ÿ›ก๏ธ Role", ["Admin", "Manager", "Engineer", "Accountant"], help="Select the employee's role.")
221
+ temp_password = st.text_input("๐Ÿ”’ Temporary Password", type="password", placeholder="Set a temporary password", help="The employee will be forced to change this on first login.")
222
+ submitted = st.form_submit_button("โœ… Save Employee")
223
+
224
+ if submitted:
225
+ if not name or not email or not phone or not designation or not temp_password:
226
+ st.error("โŒ Please fill in all fields.")
227
+ elif len(temp_password) < 8 or not any(char.isdigit() for char in temp_password) or not any(char.isupper() for char in temp_password):
228
+ st.error("โŒ Temporary password must be at least 8 characters long, include a number, and an uppercase letter.")
229
+ else:
230
+ # Normalize email before saving
231
+ normalized_email = normalize_email(email)
232
+ with st.spinner("โณ Saving employee details..."):
233
+ db.collection("Employees").document(normalized_email).set({ # Use normalized email as document ID
234
+ "name": name,
235
+ "email": normalized_email,
236
+ "phone": phone,
237
+ "designation": designation,
238
+ "role": role,
239
+ "password": temp_password,
240
+ "force_password_change": True # Enforce password change on first login
241
+ })
242
+ st.success("โœ… Employee Added Successfully!")
243
+
244
+ # Display Employees
245
+ st.subheader("๐Ÿ“‹ All Employees")
246
+ employees_data = fetch_employees()
247
+ df = pd.DataFrame(employees_data)
248
+ st.dataframe(df, use_container_width=True)
249
+
250
+ # Modify or Remove Employee
251
+ if user_role == "Admin":
252
+ st.subheader("๐Ÿ“ Modify or Remove Employee")
253
+ selected_employee = st.selectbox("๐Ÿ‘ฅ Select Employee to Modify/Remove", [emp["Email"] for emp in employees_data])
254
+
255
+ if selected_employee:
256
+ emp_doc = db.collection("Employees").document(selected_employee).get()
257
+ if emp_doc.exists:
258
+ emp_data = emp_doc.to_dict()
259
+
260
+ # Modify Employee Form
261
+ with st.form("modify_employee_form"):
262
+ st.subheader("๐Ÿ“ Modify Employee Details")
263
+ col1, col2 = st.columns(2)
264
+ with col1:
265
+ name = st.text_input("๐Ÿ‘ค Name", value=emp_data.get("name", ""), help="Full name of the employee")
266
+ email = st.text_input("๐Ÿ“ง Email", value=emp_data.get("email", ""), help="Official email address")
267
+ with col2:
268
+ phone = st.text_input("๐Ÿ“ž Phone", value=emp_data.get("phone", ""), help="Contact number")
269
+ designation = st.text_input("๐Ÿ’ผ Designation", value=emp_data.get("designation", ""), help="Job role or position")
270
+
271
+ role = st.selectbox("๐Ÿ›ก๏ธ Role", ["Admin", "Manager", "Engineer", "Accountant"], index=["Admin", "Manager", "Engineer", "Accountant"].index(emp_data.get("role", "Employee")))
272
+ submitted = st.form_submit_button("โœ… Update Employee")
273
+
274
+ if submitted:
275
+ # Normalize email before saving
276
+ normalized_email = normalize_email(email)
277
+ with st.spinner("โณ Updating employee details..."):
278
+ db.collection("Employees").document(normalized_email).update({
279
+ "name": name,
280
+ "email": normalized_email,
281
+ "phone": phone,
282
+ "designation": designation,
283
+ "role": role
284
+ })
285
+ st.success("โœ… Employee Updated Successfully!")
286
+
287
+ # Remove Employee Button
288
+ if st.button("๐Ÿ—‘๏ธ Remove Employee"):
289
+ with st.spinner("โณ Removing employee..."):
290
+ db.collection("Employees").document(selected_employee).delete()
291
+ st.success("โœ… Employee Removed Successfully!")
292
+
293
+ # Export Data as CSV
294
+ if not df.empty:
295
+ csv = df.to_csv(index=False).encode('utf-8')
296
+ st.download_button(
297
+ label="๐Ÿ“ฅ Download Employee Data as CSV",
298
+ data=csv,
299
+ file_name="employees.csv",
300
+ mime="text/csv"
301
+ )
302
+
303
+ elif menu[choice] == "role":
304
+ if user_role not in ["Admin"]:
305
+ st.error("โŒ You do not have permission to access this module.")
306
+ st.stop()
307
+
308
+ st.header("๐Ÿ›ก๏ธ Role Management")
309
+
310
+ # Add Role Form
311
+ with st.form("add_role_form"):
312
+ st.subheader("๐Ÿ“ Add Role")
313
+ role_name = st.text_input("๐Ÿ“œ Role Name", placeholder="Enter role name", help="Name of the role")
314
+ col1, col2 = st.columns(2)
315
+ with col1:
316
+ project_management = st.checkbox("๐Ÿ“Š Project Management Access", help="Grant access to project management tools")
317
+ finance_access = st.checkbox("๐Ÿ’ฐ Finance Access", help="Grant access to financial tools")
318
+ with col2:
319
+ hr_access = st.checkbox("๐Ÿ‘ฅ HR Access", help="Grant access to HR tools")
320
+
321
+ submitted = st.form_submit_button("โœ… Save Role")
322
+ if submitted:
323
+ if not role_name:
324
+ st.error("โŒ Please enter a role name.")
325
+ else:
326
+ with st.spinner("โณ Saving role details..."):
327
+ db.collection("Roles").add({
328
+ "role_name": role_name,
329
+ "permissions": {
330
+ "Project Management": project_management,
331
+ "Finance": finance_access,
332
+ "HR": hr_access
333
+ }
334
+ })
335
+ st.success(f"โœ… Role '{role_name}' Added Successfully!")
336
+
337
+ # Display Roles
338
+ st.subheader("๐Ÿ“‹ All Roles")
339
+ roles_ref = db.collection("Roles").stream()
340
+ role_data = [{"ID": role.id, "Role Name": role.to_dict()["role_name"], "Permissions": role.to_dict()["permissions"]} for role in roles_ref]
341
+ st.table(pd.DataFrame(role_data))
342
+
343
+ elif menu[choice] == "access":
344
+ st.header("๐Ÿ”’ Access Control")
345
+ st.write("Control access to modules based on roles here.")
346
+ # Example: Simulate role-based access control
347
+ user_role = st.selectbox("๐Ÿ›ก๏ธ Select User Role", ["Admin", "Manager", "Engineer", "Accountant"])
348
+ if user_role == "Admin":
349
+ st.info("โœ… Full Access Granted.")
350
+ elif user_role == "Manager":
351
+ st.info("โœ… Access to Project Management and Dashboards.")
352
+ else:
353
+ st.warning("โš ๏ธ Limited Access.")
354
+
355
+ elif menu[choice] == "dashboard":
356
+ st.header("๐Ÿ“Š Admin Dashboards")
357
+
358
+ # Employee Count by Role
359
+ st.subheader("๐Ÿ‘ฅ Employees by Role")
360
+ roles_count = {}
361
+ employees_ref = db.collection("Employees").stream()
362
+ for emp in employees_ref:
363
+ role = emp.to_dict()["role"]
364
+ roles_count[role] = roles_count.get(role, 0) + 1
365
+
366
+ roles = list(roles_count.keys())
367
+ counts = list(roles_count.values())
368
+
369
+ # Bar Chart
370
+ st.bar_chart(pd.DataFrame({"Role": roles, "Count": counts}).set_index("Role"))
371
+
372
+ # Pie Chart
373
+ st.subheader("๐Ÿ“Š Role Distribution")
374
+ fig = px.pie(names=roles, values=counts, title="Role Distribution")
375
+ st.plotly_chart(fig)
376
+
377
+ elif menu[choice] == "report":
378
+ st.header("๐Ÿ“‹ Reporting Templates")
379
+
380
+ # Add Template Form
381
+ with st.form("add_template_form"):
382
+ st.subheader("๐Ÿ“ Add Report Template")
383
+ template_name = st.text_input("๐Ÿ“„ Template Name", placeholder="Enter template name", help="Name of the report template")
384
+ fields = st.text_area("๐Ÿ“‹ Fields (comma-separated)", "Project Name, Start Date, End Date", help="Enter fields separated by commas.")
385
+
386
+ submitted = st.form_submit_button("โœ… Save Template")
387
+ if submitted:
388
+ if not template_name or not fields:
389
+ st.error("โŒ Please fill in all fields.")
390
+ else:
391
+ with st.spinner("โณ Saving report template..."):
392
+ db.collection("Reports").add({
393
+ "template_name": template_name,
394
+ "fields": fields.split(",")
395
+ })
396
+ st.success(f"โœ… Template '{template_name}' Added Successfully!")
397
+
398
+ # Display Templates
399
+ st.subheader("๐Ÿ“‹ All Templates")
400
+ templates_ref = db.collection("Reports").stream()
401
+ template_data = [{"ID": temp.id, "Template Name": temp.to_dict()["template_name"], "Fields": temp.to_dict()["fields"]} for temp in templates_ref]
402
+ st.table(pd.DataFrame(template_data))
403
+
404
+ elif menu[choice] == "chatbot":
405
+ st.header("๐Ÿค– Chatbot")
406
+
407
+ # Load QA model
408
+ qa_pipeline = pipeline("question-answering")
409
+
410
+ # Chatbot Interface
411
+ st.subheader("๐Ÿ’ฌ Ask a Question")
412
+ question = st.text_input("โ“ Your Question")
413
+ context = "The Admin module allows you to manage employees, roles, and access control. Use the sidebar to navigate."
414
+
415
+ if st.button("๐Ÿ” Get Answer"):
416
+ try:
417
+ result = qa_pipeline(question=question, context=context)
418
+ st.write("๐Ÿ’ก Answer:", result["answer"])
419
+ except Exception as e:
420
+ st.error(f"โŒ An error occurred: {e}")
421
+
422
+ elif menu[choice] == "analytics":
423
+ st.header("๐Ÿง  Advanced Analytics")
424
+
425
+ # Predictive Analytics: Role Recommendations
426
+ st.subheader("๐Ÿ‘ฅ Employee Role Recommendations")
427
+ employees_ref = db.collection("Employees").stream()
428
+ employee_data = [{"Name": emp.to_dict()["name"], "Role": emp.to_dict()["role"]} for emp in employees_ref]
429
+ df = pd.DataFrame(employee_data)
430
+
431
+ if not df.empty:
432
+ # Encode roles for clustering
433
+ role_mapping = {role: idx for idx, role in enumerate(df["Role"].unique())}
434
+ df["Role_Encoded"] = df["Role"].map(role_mapping)
435
+
436
+ # Perform K-Means Clustering
437
+ kmeans = KMeans(n_clusters=len(role_mapping), random_state=42)
438
+ df["Cluster"] = kmeans.fit_predict(df[["Role_Encoded"]])
439
+
440
+ # Display Recommendations
441
+ st.write("๐Ÿ‘ฅ Clustered Employees:")
442
+ st.dataframe(df)
443
+
444
+ # Predict Role for New Employee
445
+ st.subheader("๐ŸŽฏ Predict Role for New Employee")
446
+ new_employee_name = st.text_input("๐Ÿ‘ค New Employee Name")
447
+ if st.button("๐Ÿ”ฎ Predict Role"):
448
+ if not new_employee_name:
449
+ st.error("โŒ Please enter the employee's name.")
450
+ else:
451
+ # Randomly assign a cluster (for demonstration purposes)
452
+ predicted_cluster = np.random.randint(0, len(role_mapping))
453
+ predicted_role = [role for role, idx in role_mapping.items() if idx == predicted_cluster][0]
454
+ st.success(f"๐ŸŽฏ Recommended Role for {new_employee_name}: {predicted_role}")
455
+
456
+ # Anomaly Detection
457
+ st.subheader("โš ๏ธ Anomaly Detection in Employee Data")
458
+ if not df.empty:
459
+ # Use Isolation Forest for anomaly detection
460
+ iso_forest = IsolationForest(contamination=0.1, random_state=42)
461
+ df["Anomaly"] = iso_forest.fit_predict(df[["Role_Encoded"]])
462
+ anomalies = df[df["Anomaly"] == -1]
463
+
464
+ st.write("โš ๏ธ Detected Anomalies:")
465
+ st.dataframe(anomalies)
466
+
467
+ # Notifications Feature
468
+ st.sidebar.subheader("๐Ÿ”” Notifications")
469
+ st.sidebar.info("๐ŸŽ‰ New feature: Advanced reporting tools added!")
470
+ st.sidebar.warning("โฐ Reminder: Save your changes before navigating away.")