abraham9486937737 commited on
Commit
4778771
Β·
1 Parent(s): 24f1b84

Update KPI cards styling and layout - custom HTML cards with purple gradient, responsive columns, fix deprecation warning

Browse files
Files changed (1) hide show
  1. streamlit_app/app.py +661 -0
streamlit_app/app.py ADDED
@@ -0,0 +1,661 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Main Streamlit Dashboard Application
3
+ MySpace Ooty Data Analytics - Enhanced Interactive Dashboard
4
+ """
5
+
6
+ import streamlit as st
7
+ import pandas as pd
8
+ import numpy as np
9
+ import plotly.express as px
10
+ import plotly.graph_objects as go
11
+ from datetime import datetime, timedelta
12
+ import sys
13
+ from pathlib import Path
14
+ import warnings
15
+ warnings.filterwarnings('ignore')
16
+
17
+ # Add project root to path
18
+ project_root = Path(__file__).parent.parent
19
+ sys.path.insert(0, str(project_root))
20
+
21
+ from config.settings import *
22
+ from src.generate_powerpoint_report import PowerPointReportGenerator
23
+
24
+ # ═══════════════════════════════════════════════════════════════════════════
25
+ # PAGE CONFIGURATION & STYLING
26
+ # ═══════════════════════════════════════════════════════════════════════════
27
+
28
+ # Load logo for page icon
29
+ logo_file = project_root / "mypace-logo.png"
30
+
31
+ st.set_page_config(
32
+ page_title="🏨 MySpace Ooty Analytics",
33
+ page_icon=str(logo_file) if logo_file.exists() else "🏨",
34
+ layout="wide",
35
+ initial_sidebar_state="expanded",
36
+ )
37
+
38
+ # Custom CSS for enhanced UI and responsive design
39
+ st.markdown("""
40
+ <style>
41
+ /* Base styling */
42
+ .main { padding: 0px; }
43
+ .reportview-container { padding-top: 0px; }
44
+
45
+ /* KPI Cards */
46
+ .kpi-card {
47
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
48
+ color: white;
49
+ padding: 25px;
50
+ border-radius: 12px;
51
+ margin: 10px;
52
+ box-shadow: 0 4px 15px rgba(0,0,0,0.1);
53
+ text-align: center;
54
+ }
55
+
56
+ .kpi-value {
57
+ font-size: 32px;
58
+ font-weight: bold;
59
+ margin: 10px 0;
60
+ }
61
+
62
+ .kpi-label {
63
+ font-size: 14px;
64
+ opacity: 0.9;
65
+ text-transform: uppercase;
66
+ letter-spacing: 1px;
67
+ }
68
+
69
+ /* Section Headers */
70
+ .section-header {
71
+ border-left: 5px solid #667eea;
72
+ padding: 15px;
73
+ margin: 20px 0;
74
+ background-color: #f5f5f5;
75
+ border-radius: 5px;
76
+ }
77
+
78
+ /* Filter Box */
79
+ .filter-box {
80
+ background-color: #f0f2f6;
81
+ padding: 15px;
82
+ border-radius: 8px;
83
+ margin: 10px 0;
84
+ }
85
+
86
+ /* Responsive Design for Mobile and Tablets */
87
+ @media only screen and (max-width: 768px) {
88
+ /* Mobile: Stack elements vertically */
89
+ .kpi-card {
90
+ margin: 5px 0;
91
+ padding: 15px;
92
+ }
93
+
94
+ .kpi-value {
95
+ font-size: 24px;
96
+ }
97
+
98
+ .kpi-label {
99
+ font-size: 12px;
100
+ }
101
+
102
+ /* Adjust sidebar for mobile */
103
+ [data-testid="stSidebar"] {
104
+ width: 100% !important;
105
+ }
106
+
107
+ /* Make charts responsive */
108
+ .js-plotly-plot {
109
+ width: 100% !important;
110
+ height: auto !important;
111
+ }
112
+
113
+ /* Adjust table font size for mobile */
114
+ .dataframe {
115
+ font-size: 12px !important;
116
+ }
117
+
118
+ /* Stack columns on mobile */
119
+ .row-widget.stHorizontal {
120
+ flex-direction: column !important;
121
+ }
122
+
123
+ /* Footer text size */
124
+ .caption {
125
+ font-size: 10px !important;
126
+ }
127
+ }
128
+
129
+ /* Tablet Design */
130
+ @media only screen and (min-width: 769px) and (max-width: 1024px) {
131
+ .kpi-card {
132
+ padding: 20px;
133
+ }
134
+
135
+ .kpi-value {
136
+ font-size: 28px;
137
+ }
138
+
139
+ /* Adjust sidebar width for tablets */
140
+ [data-testid="stSidebar"] {
141
+ width: 280px !important;
142
+ }
143
+ }
144
+
145
+ /* Desktop Large Screens */
146
+ @media only screen and (min-width: 1025px) {
147
+ [data-testid="stSidebar"] {
148
+ width: 320px !important;
149
+ }
150
+ }
151
+
152
+ /* Cross-browser compatibility */
153
+ /* Firefox */
154
+ @-moz-document url-prefix() {
155
+ .kpi-card {
156
+ -moz-border-radius: 12px;
157
+ }
158
+ }
159
+
160
+ /* Safari and Chrome */
161
+ @supports (-webkit-appearance: none) {
162
+ .kpi-card {
163
+ -webkit-border-radius: 12px;
164
+ }
165
+ }
166
+
167
+ /* Improve touch targets for mobile */
168
+ @media (hover: none) and (pointer: coarse) {
169
+ button, [role="button"], select, input {
170
+ min-height: 44px !important;
171
+ min-width: 44px !important;
172
+ }
173
+ }
174
+
175
+ /* Ensure images are responsive */
176
+ img {
177
+ max-width: 100%;
178
+ height: auto;
179
+ }
180
+
181
+ /* Make plots responsive */
182
+ .plot-container {
183
+ width: 100% !important;
184
+ height: auto !important;
185
+ }
186
+
187
+ /* Streamlit specific responsive fixes */
188
+ .stPlotlyChart {
189
+ width: 100% !important;
190
+ }
191
+
192
+ /* Improve readability on small screens */
193
+ @media only screen and (max-width: 480px) {
194
+ h1 { font-size: 24px !important; }
195
+ h2 { font-size: 20px !important; }
196
+ h3 { font-size: 18px !important; }
197
+ p, li { font-size: 14px !important; }
198
+ }
199
+ </style>
200
+ """, unsafe_allow_html=True)
201
+
202
+ # ═══════════════════════════════════════════════════════════════════════════
203
+ # DATA LOADING FUNCTION
204
+ # ═══════════════════════════════════════════════════════════════════════════
205
+
206
+ @st.cache_data(ttl=3600)
207
+ def load_data():
208
+ """Load processed data from CSV"""
209
+ try:
210
+ file_path = Path(project_root) / "data" / "processed" / "data_cleaned_with_kpi.csv"
211
+ if file_path.exists():
212
+ df = pd.read_csv(file_path)
213
+ # Convert date columns
214
+ date_cols = ['Year', 'Month', 'Quarter', 'Week', 'Day']
215
+ for col in date_cols:
216
+ if col in df.columns:
217
+ if col == 'Month_Name':
218
+ df[col] = df[col].astype(str)
219
+ return df
220
+ else:
221
+ return None
222
+ except Exception as e:
223
+ st.error(f"Error loading data: {e}")
224
+ return None
225
+
226
+ @st.cache_data
227
+ def load_kpi_summary():
228
+ """Load KPI summary"""
229
+ try:
230
+ file_path = Path(project_root) / "data" / "processed" / "kpi_summary.csv"
231
+ if file_path.exists():
232
+ return pd.read_csv(file_path)
233
+ return None
234
+ except:
235
+ return None
236
+
237
+ # ═══════════════════════════════════════════════════════════════════════════
238
+ # SIDEBAR - FILTERS & NAVIGATION
239
+ # ═══════════════════════════════════════════════════════════════════════════
240
+
241
+ with st.sidebar:
242
+ # Display logo
243
+ try:
244
+ logo_path = Path(project_root) / "mypace-logo.png"
245
+ if logo_path.exists():
246
+ from PIL import Image
247
+ logo_img = Image.open(logo_path)
248
+ st.image(logo_img, use_container_width=True)
249
+ else:
250
+ # Fallback to unicode emoji logo
251
+ st.markdown("<h1 style='text-align: center; font-size: 80px;'>🏨</h1>", unsafe_allow_html=True)
252
+ except Exception as e:
253
+ st.markdown("<h1 style='text-align: center; font-size: 80px;'>🏨</h1>", unsafe_allow_html=True)
254
+
255
+ st.title("🏨 MySpace Ooty Holiday Inn")
256
+ st.markdown("πŸ“Š Data Analytics Dashboard")
257
+ st.markdown("---")
258
+
259
+ # Navigation
260
+ page = st.radio(
261
+ "πŸ“ Navigation",
262
+ ["πŸ“Š Overview", "πŸ“ˆ KPIs & Metrics", "πŸ” Data Exploration",
263
+ "πŸ“‰ Trends & Analysis", "🎯 Custom Reports"]
264
+ )
265
+
266
+ st.markdown("---")
267
+
268
+ # Load data
269
+ df = load_data()
270
+
271
+ if df is not None:
272
+ st.success("βœ“ Data Loaded Successfully!")
273
+ st.metric("Records", f"{len(df):,}")
274
+
275
+ st.markdown("---")
276
+ st.subheader("πŸ”§ Filters")
277
+
278
+ # Date range filter
279
+ if 'Year' in df.columns and 'Month' in df.columns:
280
+ year_options = sorted([y for y in df['Year'].unique() if pd.notna(y)])
281
+ selected_year = st.multiselect(
282
+ "πŸ“… Select Year(s)",
283
+ year_options,
284
+ default=year_options if year_options else [],
285
+ help="Filter by booking year"
286
+ )
287
+
288
+ month_options = sorted([m for m in df['Month'].unique() if pd.notna(m)])
289
+ selected_month = st.multiselect(
290
+ "πŸ“† Select Month(s)",
291
+ month_options,
292
+ default=month_options if month_options else [],
293
+ help="Filter by booking month (1-12)"
294
+ )
295
+ else:
296
+ selected_year = []
297
+ selected_month = []
298
+
299
+ # Filter by booking status
300
+ if 'Booking_Status' in df.columns:
301
+ status_options = [s for s in df['Booking_Status'].unique() if pd.notna(s)]
302
+ selected_status = st.multiselect(
303
+ "βœ… Select Booking Status",
304
+ status_options,
305
+ default=status_options if status_options else [],
306
+ help="Filter by booking status"
307
+ )
308
+ else:
309
+ selected_status = [None]
310
+
311
+ # Apply filters
312
+ df_filtered = df.copy()
313
+ if 'Year' in df.columns and selected_year:
314
+ df_filtered = df_filtered[df_filtered['Year'].isin(selected_year)]
315
+ if 'Month' in df.columns and selected_month:
316
+ df_filtered = df_filtered[df_filtered['Month'].isin(selected_month)]
317
+ if 'Booking_Status' in df_filtered.columns and selected_status and len(selected_status) > 0:
318
+ df_filtered = df_filtered[df_filtered['Booking_Status'].isin(selected_status)]
319
+
320
+ st.metric("Filtered Records", f"{len(df_filtered):,}")
321
+
322
+ else:
323
+ st.warning("⚠ No data found. Please run the EDA notebook first.")
324
+ df_filtered = None
325
+
326
+ st.markdown("---")
327
+ st.info("πŸ’‘ Tip: Use filters to customize your analysis")
328
+
329
+ # ═══════════════════════════════════════════════════════════════════════════
330
+ # MAIN CONTENT - PAGE ROUTING
331
+ # ═══════════════════════════════════════════════════════════════════════════
332
+
333
+ if df_filtered is None or len(df_filtered) == 0:
334
+ st.error("🚨 No data available. Please ensure the data file exists at `data/processed/data_cleaned_with_kpi.csv`")
335
+ st.stop()
336
+
337
+ # PAGE 1: OVERVIEW
338
+ if page == "πŸ“Š Overview":
339
+ st.title("πŸ“Š Dashboard Overview")
340
+ st.markdown("Get a quick summary of your business metrics")
341
+
342
+ # Expandable filters section
343
+ with st.expander("πŸ”§ Active Filters", expanded=True):
344
+ col1, col2, col3 = st.columns(3)
345
+ with col1:
346
+ st.metric("Years Selected", len(selected_year))
347
+ with col2:
348
+ st.metric("Months Selected", len(selected_month))
349
+ with col3:
350
+ st.metric("Status Selected", len(selected_status))
351
+ st.info(f"πŸ“Š Showing {len(df_filtered):,} records out of {len(df):,} total")
352
+
353
+ # Load KPI summary
354
+ kpi_summary = load_kpi_summary()
355
+
356
+ if kpi_summary is not None:
357
+ kpis_dict = dict(zip(kpi_summary['Metric'], kpi_summary['Value']))
358
+ else:
359
+ kpis_dict = {}
360
+
361
+ # KPI Cards with Custom Styling
362
+ st.markdown("### πŸ“ˆ Key Performance Indicators")
363
+
364
+ # Calculate KPI values
365
+ total_bookings = len(df_filtered)
366
+ total_revenue = df_filtered[[col for col in df_filtered.columns if 'amount' in col.lower() or 'revenue' in col.lower()]].sum().sum()
367
+
368
+ # Calculate average length of stay
369
+ if 'No. Nights' in df_filtered.columns:
370
+ avg_los = df_filtered['No. Nights'].fillna(0).astype(float).mean()
371
+ elif 'Room_Nights' in df_filtered.columns:
372
+ avg_los = df_filtered['Room_Nights'].fillna(0).astype(float).mean()
373
+ else:
374
+ nights_col = [col for col in df_filtered.columns if 'night' in col.lower()]
375
+ avg_los = df_filtered[nights_col[0]].fillna(0).astype(float).mean() if nights_col else 0
376
+
377
+ avg_revenue = total_revenue / total_bookings if total_bookings > 0 else 0
378
+
379
+ # Display KPI cards using columns
380
+ kpi_col1, kpi_col2, kpi_col3, kpi_col4 = st.columns([0.8, 1.1, 1.1, 1.1], gap="small")
381
+
382
+ with kpi_col1:
383
+ st.markdown(f"""
384
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 25px; border-radius: 12px; text-align: center; color: white; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
385
+ <div style="font-size: 12px; font-weight: 600; letter-spacing: 1px; opacity: 0.9; margin-bottom: 10px;">TOTAL RECORDS</div>
386
+ <div style="font-size: 32px; font-weight: bold;">{total_bookings:,}</div>
387
+ </div>
388
+ """, unsafe_allow_html=True)
389
+
390
+ with kpi_col2:
391
+ st.markdown(f"""
392
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 25px; border-radius: 12px; text-align: center; color: white; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
393
+ <div style="font-size: 12px; font-weight: 600; letter-spacing: 1px; opacity: 0.9; margin-bottom: 10px;">TOTAL REVENUE</div>
394
+ <div style="font-size: 32px; font-weight: bold;">β‚Ή{total_revenue:,.0f}</div>
395
+ </div>
396
+ """, unsafe_allow_html=True)
397
+
398
+ with kpi_col3:
399
+ st.markdown(f"""
400
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 25px; border-radius: 12px; text-align: center; color: white; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
401
+ <div style="font-size: 12px; font-weight: 600; letter-spacing: 1px; opacity: 0.9; margin-bottom: 10px;">AVG. LENGTH OF STAY</div>
402
+ <div style="font-size: 32px; font-weight: bold;">{avg_los:.1f} <span style="font-size: 14px; font-weight: 500;">nights</span></div>
403
+ </div>
404
+ """, unsafe_allow_html=True)
405
+
406
+ with kpi_col4:
407
+ st.markdown(f"""
408
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 25px; border-radius: 12px; text-align: center; color: white; box-shadow: 0 4px 15px rgba(0,0,0,0.1);">
409
+ <div style="font-size: 12px; font-weight: 600; letter-spacing: 1px; opacity: 0.9; margin-bottom: 10px;">REVENUE PER BOOKING</div>
410
+ <div style="font-size: 32px; font-weight: bold;">β‚Ή{avg_revenue:,.0f}</div>
411
+ </div>
412
+ """, unsafe_allow_html=True)
413
+
414
+
415
+ # Charts Row 1
416
+ st.markdown("---")
417
+ st.markdown("### πŸ“Š Analytics")
418
+
419
+ col1, col2 = st.columns(2)
420
+
421
+ with col1:
422
+ st.subheader("Bookings by Month")
423
+ if 'Month' in df_filtered.columns:
424
+ monthly_data = df_filtered.groupby('Month').size().reset_index(name='Bookings')
425
+ fig = px.bar(monthly_data, x='Month', y='Bookings',
426
+ title="Monthly Booking Distribution",
427
+ color='Bookings', color_continuous_scale='Blues')
428
+ fig.update_layout(height=400, showlegend=False)
429
+ st.plotly_chart(fig, use_container_width=True)
430
+
431
+ with col2:
432
+ st.subheader("Revenue Distribution")
433
+ revenue_col = next((col for col in df_filtered.columns if 'amount' in col.lower() or 'total' in col.lower()), None)
434
+ if revenue_col:
435
+ fig = px.histogram(df_filtered, x=revenue_col, nbins=30,
436
+ title="Revenue Distribution",
437
+ color_discrete_sequence=['#636EFA'])
438
+ fig.update_layout(height=400)
439
+ st.plotly_chart(fig, use_container_width=True)
440
+
441
+ # Charts Row 2
442
+ col1, col2 = st.columns(2)
443
+
444
+ with col1:
445
+ st.subheader("Bookings by Day of Week")
446
+ if 'Day_of_Week' in df_filtered.columns:
447
+ dow_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
448
+ dow_data = df_filtered['Day_of_Week'].value_counts().reindex(
449
+ [d for d in dow_order if d in df_filtered['Day_of_Week'].values]
450
+ ).reset_index(name='Bookings')
451
+ dow_data.columns = ['Day', 'Bookings']
452
+ fig = px.bar(dow_data, x='Day', y='Bookings',
453
+ title="Bookings by Day of Week",
454
+ color='Bookings', color_continuous_scale='Greens')
455
+ fig.update_layout(height=400)
456
+ st.plotly_chart(fig, use_container_width=True)
457
+
458
+ with col2:
459
+ st.subheader("Holiday vs Regular Season")
460
+ if 'Is_Holiday_Season' in df_filtered.columns:
461
+ season_data = df_filtered['Is_Holiday_Season'].map({1: 'Holiday Season', 0: 'Regular Season'}).value_counts()
462
+ fig = px.pie(values=season_data.values, names=season_data.index,
463
+ title="Booking Distribution by Season",
464
+ color_discrete_sequence=['#FF6B6B', '#4ECDC4'])
465
+ fig.update_layout(height=400)
466
+ st.plotly_chart(fig, use_container_width=True)
467
+
468
+ # PAGE 2: KPIs & METRICS
469
+ elif page == "πŸ“ˆ KPIs & Metrics":
470
+ st.title("πŸ“ˆ Key Performance Indicators")
471
+ st.markdown("Detailed business metrics and performance indicators")
472
+
473
+ kpi_summary = load_kpi_summary()
474
+
475
+ if kpi_summary is not None:
476
+ # Display KPI table
477
+ st.subheader("Summary Metrics")
478
+ st.dataframe(
479
+ kpi_summary.head(15),
480
+ use_container_width=True,
481
+ height=400
482
+ )
483
+
484
+ # Calculate additional metrics
485
+ st.markdown("---")
486
+ st.subheader("Performance Analysis")
487
+
488
+ col1, col2, col3 = st.columns(3)
489
+
490
+ with col1:
491
+ total_bookings = len(df_filtered)
492
+ st.info(f"πŸ“Š Total Bookings\n\n**{total_bookings:,}** bookings")
493
+
494
+ with col2:
495
+ if 'Is_Weekend' in df_filtered.columns:
496
+ weekend_bookings = (df_filtered['Is_Weekend'] == 1).sum()
497
+ pct = (weekend_bookings / len(df_filtered) * 100) if len(df_filtered) > 0 else 0
498
+ st.info(f"πŸŽ‰ Weekend Bookings\n\n**{pct:.1f}%** of total")
499
+
500
+ with col3:
501
+ if 'Is_Holiday_Season' in df_filtered.columns:
502
+ holiday_bookings = (df_filtered['Is_Holiday_Season'] == 1).sum()
503
+ pct = (holiday_bookings / len(df_filtered) * 100) if len(df_filtered) > 0 else 0
504
+ st.info(f"πŸŽ„ Holiday Season\n\n**{pct:.1f}%** of bookings")
505
+
506
+ # PAGE 3: DATA EXPLORATION
507
+ elif page == "πŸ” Data Exploration":
508
+ st.title("πŸ” Exploratory Data Analysis")
509
+
510
+ # Data Overview
511
+ st.subheader("Dataset Overview")
512
+
513
+ col1, col2, col3, col4 = st.columns(4)
514
+ with col1:
515
+ st.metric("Rows", f"{len(df_filtered):,}")
516
+ with col2:
517
+ st.metric("Columns", df_filtered.shape[1])
518
+ with col3:
519
+ st.metric("Missing Values", f"{df_filtered.isnull().sum().sum():,}")
520
+ with col4:
521
+ st.metric("Duplicates", "0")
522
+
523
+ # Display data
524
+ st.subheader("Data Sample")
525
+ st.dataframe(df_filtered.head(10), use_container_width=True)
526
+
527
+ # Column statistics
528
+ st.subheader("Column Statistics")
529
+ numeric_cols = df_filtered.select_dtypes(include=[np.number]).columns.tolist()
530
+
531
+ if numeric_cols:
532
+ selected_cols = st.multiselect("Select columns to analyze", numeric_cols, default=numeric_cols[:5])
533
+ st.dataframe(
534
+ df_filtered[selected_cols].describe().round(2),
535
+ use_container_width=True
536
+ )
537
+
538
+ # PAGE 4: TRENDS & ANALYSIS
539
+ elif page == "πŸ“‰ Trends & Analysis":
540
+ st.title("πŸ“‰ Trends & Statistical Analysis")
541
+
542
+ # Monthly Trend
543
+ st.subheader("Monthly Trends")
544
+ if 'Month' in df_filtered.columns:
545
+ monthly_bookings = df_filtered.groupby('Month').size()
546
+ fig = px.line(x=monthly_bookings.index, y=monthly_bookings.values,
547
+ labels={'x': 'Month', 'y': 'Bookings'},
548
+ title="Booking Trend Over Months",
549
+ markers=True)
550
+ fig.update_traces(line=dict(color='#FF6B6B', width=3), marker=dict(size=10))
551
+ st.plotly_chart(fig, use_container_width=True)
552
+
553
+ # Revenue Trends
554
+ st.markdown("---")
555
+ st.subheader("Revenue Trends")
556
+
557
+ revenue_cols = [col for col in df_filtered.columns if any(kw in col.lower() for kw in ['amount', 'revenue', 'total'])]
558
+ if revenue_cols and 'Month' in df_filtered.columns:
559
+ revenue_col = revenue_cols[0]
560
+ monthly_revenue = df_filtered.groupby('Month')[revenue_col].sum()
561
+ fig = px.line(x=monthly_revenue.index, y=monthly_revenue.values,
562
+ labels={'x': 'Month', 'y': 'Revenue (β‚Ή)'},
563
+ title="Revenue Trend Over Months",
564
+ markers=True)
565
+ fig.update_traces(line=dict(color='#00CC96', width=3), marker=dict(size=10))
566
+ st.plotly_chart(fig, use_container_width=True)
567
+
568
+ # PAGE 5: CUSTOM REPORTS
569
+ elif page == "🎯 Custom Reports":
570
+ st.title("🎯 Custom Reports & Export")
571
+ st.markdown("Generate personalized reports with selected filters")
572
+
573
+ # Report options
574
+ st.subheader("Report Configuration")
575
+
576
+ col1, col2 = st.columns(2)
577
+
578
+ with col1:
579
+ report_type = st.selectbox(
580
+ "Report Type",
581
+ ["Summary Report", "Detailed Analysis", "Executive Summary", "Seasonal Analysis"]
582
+ )
583
+
584
+ with col2:
585
+ export_format = st.selectbox(
586
+ "Export Format",
587
+ ["CSV", "Excel", "PDF (Coming Soon)"]
588
+ )
589
+
590
+ # Report preview
591
+ st.subheader("Report Preview")
592
+ st.info(f"πŸ“„ {report_type} - {len(df_filtered):,} records")
593
+ st.dataframe(df_filtered.head(20), use_container_width=True)
594
+
595
+ # Export button
596
+ col1, col2, col3 = st.columns(3)
597
+
598
+ with col1:
599
+ if st.button("πŸ“₯ Download CSV", key="csv"):
600
+ csv = df_filtered.to_csv(index=False)
601
+ st.download_button(
602
+ label="CSV Report",
603
+ data=csv,
604
+ file_name=f"myspace_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
605
+ mime="text/csv"
606
+ )
607
+
608
+ with col2:
609
+ if st.button("πŸ“Š Download Excel", key="excel"):
610
+ excel_buffer = pd.ExcelWriter('/tmp/report.xlsx', engine='openpyxl')
611
+ df_filtered.to_excel(excel_buffer, index=False)
612
+ excel_buffer.close()
613
+ with open('/tmp/report.xlsx', 'rb') as f:
614
+ st.download_button(
615
+ label="Excel Report",
616
+ data=f.read(),
617
+ file_name=f"myspace_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx",
618
+ mime="application/vnd.ms-excel"
619
+ )
620
+
621
+ with col3:
622
+ if st.button("πŸ“Š Generate PowerPoint", key="ppt"):
623
+ with st.spinner("πŸ”„ Generating PowerPoint presentation..."):
624
+ try:
625
+ # Generate PowerPoint report
626
+ generator = PowerPointReportGenerator()
627
+ ppt_path = generator.generate_report()
628
+
629
+ # Read the file and provide download
630
+ with open(ppt_path, 'rb') as f:
631
+ st.download_button(
632
+ label="πŸ“₯ Download PowerPoint",
633
+ data=f.read(),
634
+ file_name=f"MySpace_Ooty_Report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pptx",
635
+ mime="application/vnd.openxmlformats-officedocument.presentationml.presentation"
636
+ )
637
+ st.success("βœ… PowerPoint report generated successfully!")
638
+ except Exception as e:
639
+ st.error(f"❌ Error generating PowerPoint: {str(e)}")
640
+
641
+ # Footer
642
+ st.markdown("---")
643
+ st.markdown("")
644
+ with st.container():
645
+ st.markdown("<h4 style='text-align: center; color: #193264;'>🏨 MySpace Holiday Inn - Ooty</h4>", unsafe_allow_html=True)
646
+
647
+ col1, col2, col3 = st.columns([1, 2, 1])
648
+ with col2:
649
+ st.markdown("""
650
+ <div style='text-align: center; font-size: 12px;'>
651
+ <p><b>πŸ“ HEAD OFFICE</b><br/>Kotagiri – 643217</p>
652
+ <p><b>πŸ“ž CONTACT US</b><br/>
653
+ +91 82206 62206 | +91-6369052954 | +91-6369973006<br/>
654
+ πŸ“§ myspaceholidayinn@gmail.com<br/>
655
+ πŸ“± +916381911228</p>
656
+ <p><b>πŸ• TIMINGS</b><br/>
657
+ Check-In: 12:00 PM | Check-Out: 10:00 AM</p>
658
+ </div>
659
+ """, unsafe_allow_html=True)
660
+
661
+ st.caption(f"πŸ“Š Data Analytics Dashboard | Last Updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")