abraham9486937737
Simplify logo loading: prioritize streamlit_app directory
a77dd7c
"""
Main Streamlit Dashboard Application
MySpace Ooty Data Analytics - Enhanced Interactive Dashboard
"""
import streamlit as st
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime, timedelta
import sys
from pathlib import Path
import warnings
warnings.filterwarnings('ignore')
# Add project root to path
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
LOGO_B64 = """
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
"""
from config.settings import *
from src.generate_powerpoint_report import PowerPointReportGenerator
# ═══════════════════════════════════════════════════════════════════════════
# PAGE CONFIGURATION & STYLING
# ═══════════════════════════════════════════════════════════════════════════
# Load logo for page icon
logo_file = project_root / "mypace-logo.png"
st.set_page_config(
page_title="🏨 MySpace Ooty Analytics",
page_icon=str(logo_file) if logo_file.exists() else "🏨",
layout="wide",
initial_sidebar_state="expanded",
)
# Custom CSS for enhanced UI and responsive design
st.markdown("""
<style>
/* Base styling */
.main { padding: 0px; }
.reportview-container { padding-top: 0px; }
/* KPI Cards */
.kpi-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 25px;
border-radius: 12px;
margin: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
text-align: center;
}
.kpi-value {
font-size: 32px;
font-weight: bold;
margin: 10px 0;
}
.kpi-label {
font-size: 14px;
opacity: 0.9;
text-transform: uppercase;
letter-spacing: 1px;
}
/* Section Headers */
.section-header {
border-left: 5px solid #667eea;
padding: 15px;
margin: 20px 0;
background-color: #f5f5f5;
border-radius: 5px;
}
/* Filter Box */
.filter-box {
background-color: #f0f2f6;
padding: 15px;
border-radius: 8px;
margin: 10px 0;
}
/* Responsive Design for Mobile and Tablets */
@media only screen and (max-width: 768px) {
/* Mobile: Stack elements vertically */
.kpi-card {
margin: 5px 0;
padding: 15px;
}
.kpi-value {
font-size: 24px;
}
.kpi-label {
font-size: 12px;
}
/* Adjust sidebar for mobile */
[data-testid="stSidebar"] {
width: 100% !important;
}
/* Make charts responsive */
.js-plotly-plot {
width: 100% !important;
height: auto !important;
}
/* Adjust table font size for mobile */
.dataframe {
font-size: 12px !important;
}
/* Stack columns on mobile */
.row-widget.stHorizontal {
flex-direction: column !important;
}
/* Footer text size */
.caption {
font-size: 10px !important;
}
}
/* Tablet Design */
@media only screen and (min-width: 769px) and (max-width: 1024px) {
.kpi-card {
padding: 20px;
}
.kpi-value {
font-size: 28px;
}
/* Adjust sidebar width for tablets */
[data-testid="stSidebar"] {
width: 280px !important;
}
}
/* Desktop Large Screens */
@media only screen and (min-width: 1025px) {
[data-testid="stSidebar"] {
width: 320px !important;
}
}
/* Cross-browser compatibility */
/* Firefox */
@-moz-document url-prefix() {
.kpi-card {
-moz-border-radius: 12px;
}
}
/* Safari and Chrome */
@supports (-webkit-appearance: none) {
.kpi-card {
-webkit-border-radius: 12px;
}
}
/* Improve touch targets for mobile */
@media (hover: none) and (pointer: coarse) {
button, [role="button"], select, input {
min-height: 44px !important;
min-width: 44px !important;
}
}
/* Ensure images are responsive */
img {
max-width: 100%;
height: auto;
}
/* Make plots responsive */
.plot-container {
width: 100% !important;
height: auto !important;
}
/* Streamlit specific responsive fixes */
.stPlotlyChart {
width: 100% !important;
}
/* Improve readability on small screens */
@media only screen and (max-width: 480px) {
h1 { font-size: 24px !important; }
h2 { font-size: 20px !important; }
h3 { font-size: 18px !important; }
p, li { font-size: 14px !important; }
}
</style>
""", unsafe_allow_html=True)
# ═══════════════════════════════════════════════════════════════════════════
# DATA LOADING FUNCTION
# ═══════════════════════════════════════════════════════════════════════════
@st.cache_data(ttl=3600)
def load_data():
"""Load processed data from CSV"""
try:
file_path = Path(project_root) / "data" / "processed" / "data_cleaned_with_kpi.csv"
if file_path.exists():
df = pd.read_csv(file_path)
# Convert date columns
date_cols = ['Year', 'Month', 'Quarter', 'Week', 'Day']
for col in date_cols:
if col in df.columns:
if col == 'Month_Name':
df[col] = df[col].astype(str)
return df
else:
return None
except Exception as e:
st.error(f"Error loading data: {e}")
return None
@st.cache_data
def load_kpi_summary():
"""Load KPI summary"""
try:
file_path = Path(project_root) / "data" / "processed" / "kpi_summary.csv"
if file_path.exists():
return pd.read_csv(file_path)
return None
except:
return None
# ═══════════════════════════════════════════════════════════════════════════
# SIDEBAR - FILTERS & NAVIGATION
# ═══════════════════════════════════════════════════════════════════════════
with st.sidebar:
# Display logo
try:
from PIL import Image
# Try to load logo from streamlit_app directory first (most direct path)
logo_direct = Path(__file__).parent / "mypace-logo.png"
if logo_direct.exists():
# Load directly from streamlit_app folder
logo_img = Image.open(logo_direct)
st.image(logo_img, use_container_width=True)
else:
# Fallback: Try other paths
logo_candidates = [
Path(project_root) / "mypace-logo.png",
Path.cwd() / "mypace-logo.png",
]
logo_path = None
for candidate in logo_candidates:
if candidate.exists():
logo_path = candidate
break
if logo_path:
logo_img = Image.open(logo_path)
st.image(logo_img, use_container_width=True)
else:
# Try HF URL
hf_logo_url = "https://huggingface.co/spaces/abrahamcbe/myspace-ooty-analytics/resolve/main/mypace-logo.png"
try:
st.image(hf_logo_url, use_container_width=True)
except Exception:
# Use base64
try:
logo_b64 = "".join(LOGO_B64.split())
st.image(f"data:image/png;base64,{logo_b64}", use_container_width=True)
except Exception:
st.markdown("<h1 style='text-align: center; font-size: 80px;'>🏨</h1>", unsafe_allow_html=True)
except Exception as e:
st.markdown("<h1 style='text-align: center; font-size: 80px;'>🏨</h1>", unsafe_allow_html=True)
st.title("🏨 MySpace Ooty Holiday Inn")
st.markdown("πŸ“Š Data Analytics Dashboard")
st.markdown("---")
# Navigation
page = st.radio(
"πŸ“ Navigation",
["πŸ“Š Overview", "πŸ“ˆ KPIs & Metrics", "πŸ” Data Exploration",
"πŸ“‰ Trends & Analysis", "🎯 Custom Reports"]
)
st.markdown("---")
# Load data
df = load_data()
if df is not None:
st.success("βœ“ Data Loaded Successfully!")
st.metric("Records", f"{len(df):,}")
st.markdown("---")
st.subheader("πŸ”§ Filters")
# Date range filter
if 'Year' in df.columns and 'Month' in df.columns:
year_options = sorted([y for y in df['Year'].unique() if pd.notna(y)])
selected_year = st.multiselect(
"πŸ“… Select Year(s)",
year_options,
default=year_options if year_options else [],
help="Filter by booking year"
)
month_options = sorted([m for m in df['Month'].unique() if pd.notna(m)])
selected_month = st.multiselect(
"πŸ“† Select Month(s)",
month_options,
default=month_options if month_options else [],
help="Filter by booking month (1-12)"
)
else:
selected_year = []
selected_month = []
# Filter by booking status
if 'Booking_Status' in df.columns:
status_options = [s for s in df['Booking_Status'].unique() if pd.notna(s)]
selected_status = st.multiselect(
"βœ… Select Booking Status",
status_options,
default=status_options if status_options else [],
help="Filter by booking status"
)
else:
selected_status = [None]
# Apply filters
df_filtered = df.copy()
if 'Year' in df.columns and selected_year:
df_filtered = df_filtered[df_filtered['Year'].isin(selected_year)]
if 'Month' in df.columns and selected_month:
df_filtered = df_filtered[df_filtered['Month'].isin(selected_month)]
if 'Booking_Status' in df_filtered.columns and selected_status and len(selected_status) > 0:
df_filtered = df_filtered[df_filtered['Booking_Status'].isin(selected_status)]
st.metric("Filtered Records", f"{len(df_filtered):,}")
else:
st.warning("⚠ No data found. Please run the EDA notebook first.")
df_filtered = None
st.markdown("---")
st.info("πŸ’‘ Tip: Use filters to customize your analysis")
# ═══════════════════════════════════════════════════════════════════════════
# MAIN CONTENT - PAGE ROUTING
# ═══════════════════════════════════════════════════════════════════════════
if df_filtered is None or len(df_filtered) == 0:
st.error("🚨 No data available. Please ensure the data file exists at `data/processed/data_cleaned_with_kpi.csv`")
st.stop()
# PAGE 1: OVERVIEW
if page == "πŸ“Š Overview":
st.title("πŸ“Š Dashboard Overview")
st.markdown("Get a quick summary of your business metrics")
# Expandable filters section
with st.expander("πŸ”§ Active Filters", expanded=True):
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Years Selected", len(selected_year))
with col2:
st.metric("Months Selected", len(selected_month))
with col3:
st.metric("Status Selected", len(selected_status))
st.info(f"πŸ“Š Showing {len(df_filtered):,} records out of {len(df):,} total")
# Load KPI summary
kpi_summary = load_kpi_summary()
if kpi_summary is not None:
kpis_dict = dict(zip(kpi_summary['Metric'], kpi_summary['Value']))
else:
kpis_dict = {}
# KPI Cards with Custom Styling
st.markdown("### πŸ“ˆ Key Performance Indicators")
# Calculate KPI values
total_bookings = len(df_filtered)
total_revenue = df_filtered[[col for col in df_filtered.columns if 'amount' in col.lower() or 'revenue' in col.lower()]].sum().sum()
# Calculate average length of stay
if 'No. Nights' in df_filtered.columns:
avg_los = df_filtered['No. Nights'].fillna(0).astype(float).mean()
elif 'Room_Nights' in df_filtered.columns:
avg_los = df_filtered['Room_Nights'].fillna(0).astype(float).mean()
else:
nights_col = [col for col in df_filtered.columns if 'night' in col.lower()]
avg_los = df_filtered[nights_col[0]].fillna(0).astype(float).mean() if nights_col else 0
avg_revenue = total_revenue / total_bookings if total_bookings > 0 else 0
# Display KPI cards using columns
kpi_col1, kpi_col2, kpi_col3, kpi_col4 = st.columns([0.8, 1.1, 1.1, 1.1], gap="small")
with kpi_col1:
st.markdown(f"""
<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);">
<div style="font-size: 12px; font-weight: 600; letter-spacing: 1px; opacity: 0.9; margin-bottom: 10px;">TOTAL RECORDS</div>
<div style="font-size: 32px; font-weight: bold;">{total_bookings:,}</div>
</div>
""", unsafe_allow_html=True)
with kpi_col2:
st.markdown(f"""
<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);">
<div style="font-size: 12px; font-weight: 600; letter-spacing: 1px; opacity: 0.9; margin-bottom: 10px;">TOTAL REVENUE</div>
<div style="font-size: 32px; font-weight: bold;">β‚Ή{total_revenue:,.0f}</div>
</div>
""", unsafe_allow_html=True)
with kpi_col3:
st.markdown(f"""
<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);">
<div style="font-size: 12px; font-weight: 600; letter-spacing: 1px; opacity: 0.9; margin-bottom: 10px;">AVG. LENGTH OF STAY</div>
<div style="font-size: 32px; font-weight: bold;">{avg_los:.1f} <span style="font-size: 14px; font-weight: 500;">nights</span></div>
</div>
""", unsafe_allow_html=True)
with kpi_col4:
st.markdown(f"""
<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);">
<div style="font-size: 12px; font-weight: 600; letter-spacing: 1px; opacity: 0.9; margin-bottom: 10px;">REVENUE PER BOOKING</div>
<div style="font-size: 32px; font-weight: bold;">β‚Ή{avg_revenue:,.0f}</div>
</div>
""", unsafe_allow_html=True)
# Charts Row 1
st.markdown("---")
st.markdown("### πŸ“Š Analytics")
col1, col2 = st.columns(2)
with col1:
st.subheader("Bookings by Month")
month_series = None
if 'Month' in df_filtered.columns:
month_series = df_filtered['Month']
else:
date_col = next((c for c in df_filtered.columns if 'date' in c.lower()), None)
if date_col:
month_series = pd.to_datetime(df_filtered[date_col], errors='coerce').dt.month
if month_series is not None:
month_series = pd.to_numeric(month_series, errors='coerce').dropna().astype(int)
if not month_series.empty:
month_order = list(range(1, 13))
monthly_data = month_series.value_counts().reindex(month_order, fill_value=0).reset_index()
monthly_data.columns = ['MonthNum', 'Bookings']
monthly_data['Month'] = pd.to_datetime(monthly_data['MonthNum'], format='%m').dt.strftime('%b')
fig = px.bar(
monthly_data,
x='Month',
y='Bookings',
title="Monthly Booking Distribution",
color='Bookings',
color_continuous_scale='Blues'
)
fig.update_layout(height=400, showlegend=False)
st.plotly_chart(fig, use_container_width=True)
else:
st.info("No month data available for the current filters.")
else:
st.info("Month data not found. Add a Month or Date column to plot this chart.")
with col2:
st.subheader("Revenue Distribution")
# Prioritize certain columns for revenue
priority_cols = ['Total Amount', 'Booking Amount', 'Received Amount']
revenue_candidates = [
col for col in df_filtered.columns
if any(k in col.lower() for k in ['amount', 'revenue', 'total', 'rate', 'price', 'cost'])
]
# Sort by priority
revenue_candidates = sorted(
revenue_candidates,
key=lambda x: (priority_cols.index(x) if x in priority_cols else len(priority_cols))
)
best_col = None
best_score = 0
for col in revenue_candidates:
cleaned = (
df_filtered[col]
.astype(str)
.str.replace(r'[^0-9.-]', '', regex=True)
)
vals = pd.to_numeric(cleaned, errors='coerce')
# Filter to positive values only (revenue should be > 0)
positive_vals = vals[vals > 0]
score = len(positive_vals)
if score > best_score and positive_vals.sum() > 0:
best_score = score
best_col = col
if best_col:
cleaned = (
df_filtered[best_col]
.astype(str)
.str.replace(r'[^0-9.-]', '', regex=True)
)
revenue_values = pd.to_numeric(cleaned, errors='coerce')
# Filter to positive values only
revenue_values = revenue_values[revenue_values > 0].dropna()
if revenue_values.empty:
st.info(f"No positive revenue values found in {best_col}")
elif len(revenue_values) <= 5:
st.info("Insufficient data for distribution chart")
else:
try:
# Create histogram using DataFrame format for better Streamlit compatibility
nbins = min(30, max(5, len(revenue_values) // 10))
df_hist = pd.DataFrame({'Revenue': revenue_values})
fig = px.histogram(
df_hist,
x='Revenue',
nbins=nbins,
title=f"Revenue Distribution - {best_col}",
color_discrete_sequence=['#636EFA']
)
fig.update_layout(
height=400,
xaxis_title=best_col,
yaxis_title="Count",
hovermode='x unified',
showlegend=False
)
fig.update_traces(marker=dict(line=dict(width=0)))
st.plotly_chart(fig, use_container_width=True)
except Exception as e:
st.error(f"Error creating histogram: {str(e)}")
# Fallback: show summary stats in columns
col_stats1, col_stats2, col_stats3 = st.columns(3)
with col_stats1:
st.metric("Average", f"β‚Ή{revenue_values.mean():,.0f}")
with col_stats2:
st.metric("Median", f"β‚Ή{revenue_values.median():,.0f}")
with col_stats3:
st.metric("Max", f"β‚Ή{revenue_values.max():,.0f}")
else:
st.info("Revenue column not found. Add a revenue/amount column to plot this chart.")
# Charts Row 2
col1, col2 = st.columns(2)
with col1:
st.subheader("Bookings by Day of Week")
if 'Day_of_Week' in df_filtered.columns:
dow_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
dow_data = df_filtered['Day_of_Week'].value_counts().reindex(
[d for d in dow_order if d in df_filtered['Day_of_Week'].values]
).reset_index(name='Bookings')
dow_data.columns = ['Day', 'Bookings']
fig = px.bar(dow_data, x='Day', y='Bookings',
title="Bookings by Day of Week",
color='Bookings', color_continuous_scale='Greens')
fig.update_layout(height=400)
st.plotly_chart(fig, use_container_width=True)
with col2:
st.subheader("Holiday vs Regular Season")
if 'Is_Holiday_Season' in df_filtered.columns:
season_data = df_filtered['Is_Holiday_Season'].map({1: 'Holiday Season', 0: 'Regular Season'}).value_counts()
fig = px.pie(values=season_data.values, names=season_data.index,
title="Booking Distribution by Season",
color_discrete_sequence=['#FF6B6B', '#4ECDC4'])
fig.update_layout(height=400)
st.plotly_chart(fig, use_container_width=True)
# PAGE 2: KPIs & METRICS
elif page == "πŸ“ˆ KPIs & Metrics":
st.title("πŸ“ˆ Key Performance Indicators")
st.markdown("Detailed business metrics and performance indicators")
kpi_summary = load_kpi_summary()
if kpi_summary is not None:
# Display KPI table
st.subheader("Summary Metrics")
st.dataframe(
kpi_summary.head(15),
use_container_width=True,
height=400
)
# Calculate additional metrics
st.markdown("---")
st.subheader("Performance Analysis")
col1, col2, col3 = st.columns(3)
with col1:
total_bookings = len(df_filtered)
st.info(f"πŸ“Š Total Bookings\n\n**{total_bookings:,}** bookings")
with col2:
if 'Is_Weekend' in df_filtered.columns:
weekend_bookings = (df_filtered['Is_Weekend'] == 1).sum()
pct = (weekend_bookings / len(df_filtered) * 100) if len(df_filtered) > 0 else 0
st.info(f"πŸŽ‰ Weekend Bookings\n\n**{pct:.1f}%** of total")
with col3:
if 'Is_Holiday_Season' in df_filtered.columns:
holiday_bookings = (df_filtered['Is_Holiday_Season'] == 1).sum()
pct = (holiday_bookings / len(df_filtered) * 100) if len(df_filtered) > 0 else 0
st.info(f"πŸŽ„ Holiday Season\n\n**{pct:.1f}%** of bookings")
# PAGE 3: DATA EXPLORATION
elif page == "πŸ” Data Exploration":
st.title("πŸ” Exploratory Data Analysis")
# Data Overview
st.subheader("Dataset Overview")
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Rows", f"{len(df_filtered):,}")
with col2:
st.metric("Columns", df_filtered.shape[1])
with col3:
st.metric("Missing Values", f"{df_filtered.isnull().sum().sum():,}")
with col4:
st.metric("Duplicates", "0")
# Display data
st.subheader("Data Sample")
st.dataframe(df_filtered.head(10), use_container_width=True)
# Column statistics
st.subheader("Column Statistics")
numeric_cols = df_filtered.select_dtypes(include=[np.number]).columns.tolist()
if numeric_cols:
selected_cols = st.multiselect("Select columns to analyze", numeric_cols, default=numeric_cols[:5])
st.dataframe(
df_filtered[selected_cols].describe().round(2),
use_container_width=True
)
# PAGE 4: TRENDS & ANALYSIS
elif page == "πŸ“‰ Trends & Analysis":
st.title("πŸ“‰ Trends & Statistical Analysis")
# Monthly Trend
st.subheader("Monthly Trends")
if 'Month' in df_filtered.columns:
monthly_bookings = df_filtered.groupby('Month').size()
fig = px.line(x=monthly_bookings.index, y=monthly_bookings.values,
labels={'x': 'Month', 'y': 'Bookings'},
title="Booking Trend Over Months",
markers=True)
fig.update_traces(line=dict(color='#FF6B6B', width=3), marker=dict(size=10))
st.plotly_chart(fig, use_container_width=True)
# Revenue Trends
st.markdown("---")
st.subheader("Revenue Trends")
revenue_cols = [col for col in df_filtered.columns if any(kw in col.lower() for kw in ['amount', 'revenue', 'total'])]
if revenue_cols and 'Month' in df_filtered.columns:
revenue_col = revenue_cols[0]
monthly_revenue = df_filtered.groupby('Month')[revenue_col].sum()
fig = px.line(x=monthly_revenue.index, y=monthly_revenue.values,
labels={'x': 'Month', 'y': 'Revenue (β‚Ή)'},
title="Revenue Trend Over Months",
markers=True)
fig.update_traces(line=dict(color='#00CC96', width=3), marker=dict(size=10))
st.plotly_chart(fig, use_container_width=True)
# PAGE 5: CUSTOM REPORTS
elif page == "🎯 Custom Reports":
st.title("🎯 Custom Reports & Export")
st.markdown("Generate personalized reports with selected filters")
# Report options
st.subheader("Report Configuration")
col1, col2 = st.columns(2)
with col1:
report_type = st.selectbox(
"Report Type",
["Summary Report", "Detailed Analysis", "Executive Summary", "Seasonal Analysis"]
)
with col2:
export_format = st.selectbox(
"Export Format",
["CSV", "Excel", "PDF (Coming Soon)"]
)
# Report preview
st.subheader("Report Preview")
st.info(f"πŸ“„ {report_type} - {len(df_filtered):,} records")
st.dataframe(df_filtered.head(20), use_container_width=True)
# Export button
col1, col2, col3 = st.columns(3)
with col1:
if st.button("πŸ“₯ Download CSV", key="csv"):
csv = df_filtered.to_csv(index=False)
st.download_button(
label="CSV Report",
data=csv,
file_name=f"myspace_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",
mime="text/csv"
)
with col2:
if st.button("πŸ“Š Download Excel", key="excel"):
excel_buffer = pd.ExcelWriter('/tmp/report.xlsx', engine='openpyxl')
df_filtered.to_excel(excel_buffer, index=False)
excel_buffer.close()
with open('/tmp/report.xlsx', 'rb') as f:
st.download_button(
label="Excel Report",
data=f.read(),
file_name=f"myspace_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx",
mime="application/vnd.ms-excel"
)
with col3:
if st.button("πŸ“Š Generate PowerPoint", key="ppt"):
with st.spinner("πŸ”„ Generating PowerPoint presentation..."):
try:
# Generate PowerPoint report
generator = PowerPointReportGenerator()
ppt_path = generator.generate_report()
# Read the file and provide download
with open(ppt_path, 'rb') as f:
st.download_button(
label="πŸ“₯ Download PowerPoint",
data=f.read(),
file_name=f"MySpace_Ooty_Report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pptx",
mime="application/vnd.openxmlformats-officedocument.presentationml.presentation"
)
st.success("βœ… PowerPoint report generated successfully!")
except Exception as e:
st.error(f"❌ Error generating PowerPoint: {str(e)}")
# Footer
st.markdown("---")
st.markdown("")
with st.container():
st.markdown("<h4 style='text-align: center; color: #193264;'>🏨 MySpace Holiday Inn - Ooty</h4>", unsafe_allow_html=True)
col1, col2, col3 = st.columns([1, 2, 1])
with col2:
st.markdown("""
<div style='text-align: center; font-size: 12px;'>
<p><b>πŸ“ HEAD OFFICE</b><br/>Kotagiri – 643217</p>
<p><b>πŸ“ž CONTACT US</b><br/>
+91 82206 62206 | +91-6369052954 | +91-6369973006<br/>
πŸ“§ myspaceholidayinn@gmail.com<br/>
πŸ“± +916381911228</p>
<p><b>πŸ• TIMINGS</b><br/>
Check-In: 12:00 PM | Check-Out: 10:00 AM</p>
</div>
""", unsafe_allow_html=True)
st.caption(f"πŸ“Š Data Analytics Dashboard | Last Updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")