367 lines
19 KiB
PHP
367 lines
19 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Community Command Center</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap5.min.css">
|
|
<link rel="stylesheet" href="https://cdn.datatables.net/buttons/2.4.2/css/buttons.bootstrap5.min.css">
|
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
</head>
|
|
<body>
|
|
|
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
|
<div class="container-fluid">
|
|
<a class="navbar-brand" href="#">
|
|
<img src="assets/pasted-20251230-211133-cc45faf9.png" alt="Company Logo" style="height: 40px;">
|
|
</a>
|
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
|
<span class="navbar-toggler-icon"></span>
|
|
</button>
|
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
|
<li class="nav-item">
|
|
<a class="nav-link active" aria-current="page" href="#">Dashboard</a>
|
|
</li>
|
|
</ul>
|
|
<span class="navbar-text me-3">
|
|
<i class="bi bi-geo-alt-fill"></i> Valencia Sound
|
|
</span>
|
|
<span class="navbar-text">
|
|
<i class="bi bi-calendar-fill"></i> December 2025
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="container-fluid p-4">
|
|
<div class="row g-4">
|
|
<!-- Participation & Usage -->
|
|
<div class="col-12">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-primary text-white">
|
|
<h5 class="mb-0">1. Participation & Usage</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row text-center">
|
|
<div class="col-md-3 col-6 kpi-container" data-kpi="unique_members" data-title="Unique Members">
|
|
<div id="unique_members_kpi" class="kpi-value text-primary">342</div>
|
|
<div class="kpi-label">Unique Members</div>
|
|
</div>
|
|
<div class="col-md-3 col-6 kpi-container" data-kpi="avg_class_attendance" data-title="Avg Class Attendance">
|
|
<div id="avg_class_attendance_kpi" class="kpi-value text-primary">12</div>
|
|
<div class="kpi-label">Avg Class Attendance</div>
|
|
</div>
|
|
<div class="col-md-3 col-6 mt-3 mt-md-0 kpi-container" data-kpi="classes_lightly_attended" data-title="Classes Lightly Attended">
|
|
<div id="classes_lightly_attended_kpi" class="kpi-value text-primary">15%</div>
|
|
<div class="kpi-label">Classes Lightly Attended</div>
|
|
</div>
|
|
<div class="col-md-3 col-6 mt-3 mt-md-0 kpi-container" data-kpi="attendance_trend" data-title="Attendance Trend">
|
|
<div id="attendance_trend_kpi" class="kpi-value text-success">+5% <i class="bi bi-arrow-up-right"></i></div>
|
|
<div class="kpi-label">Attendance Trend</div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-4">
|
|
<canvas id="attendance_chart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Member Engagement Depth -->
|
|
<div class="col-12">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-primary text-white">
|
|
<h5 class="mb-0">2. Member Engagement Depth</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row text-center">
|
|
<div class="col-md-6 col-6 kpi-container" data-kpi="avg_visits_per_member" data-title="Avg Visits per Member">
|
|
<div id="avg_visits_per_member_kpi" class="kpi-value text-primary">4.2</div>
|
|
<div class="kpi-label">Avg Visits per Member</div>
|
|
</div>
|
|
<div class="col-md-6 col-6 kpi-container" data-kpi="high_frequency_members" data-title="High Frequency Members">
|
|
<div id="high_frequency_members_kpi" class="kpi-value text-primary">45%</div>
|
|
<div class="kpi-label">High Frequency Members</div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-4">
|
|
<canvas id="visit_frequency_chart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Revenue Performance -->
|
|
<div class="col-12">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-success text-white">
|
|
<h5 class="mb-0">3. Revenue Performance</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row text-center">
|
|
<div class="col-md-6 col-6 kpi-container" data-kpi="total_revenue" data-title="Total Revenue">
|
|
<div id="total_revenue_kpi" class="kpi-value text-success">$22,450</div>
|
|
<div class="kpi-label">Total Revenue</div>
|
|
</div>
|
|
<div class="col-md-6 col-6 kpi-container" data-kpi="revenue_per_active_member" data-title="Revenue per Active Member">
|
|
<div id="revenue_per_active_member_kpi" class="kpi-value text-success">$65.64</div>
|
|
<div class="kpi-label">Revenue per Active Member</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Engagement Risk -->
|
|
<div class="col-12">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-danger text-white">
|
|
<h5 class="mb-0">4. Engagement Risk</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row text-center">
|
|
<div class="col-md-6 col-6 kpi-container" data-kpi="inactive_members" data-title="Inactive Members">
|
|
<div id="inactive_members_kpi" class="kpi-value text-danger">0%</div>
|
|
<div class="kpi-label">Inactive Members</div>
|
|
</div>
|
|
<div class="col-md-6 col-6 kpi-container" data-kpi="visits_dropped" data-title="Visits Dropped">
|
|
<div id="visits_dropped_kpi" class="kpi-value text-danger">0% <i class="bi bi-arrow-down-right"></i></div>
|
|
<div class="kpi-label">Visits Dropped</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Member Feedback -->
|
|
<div class="col-12">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-info text-white">
|
|
<h5 class="mb-0">5. Member Feedback</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<h6><i class="bi bi-hand-thumbs-up-fill text-success"></i> Top Praises</h6>
|
|
<ul class="list-unstyled">
|
|
<li>"Love the new yoga class!"</li>
|
|
<li>"Instructor was fantastic."</li>
|
|
</ul>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<h6><i class="bi bi-lightbulb-fill text-warning"></i> Top Requests</h6>
|
|
<ul class="list-unstyled">
|
|
<li>"More evening classes."</li>
|
|
<li>"Wish there was a smoothie bar."</li>
|
|
</ul>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<h6><i class="bi bi-exclamation-triangle-fill text-danger"></i> Top Frictions</h6>
|
|
<ul class="list-unstyled">
|
|
<li>"Locker room needs attention."</li>
|
|
<li>"Difficulty booking online."</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Alignment Insight -->
|
|
<div class="col-12">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header" style="background-color: #ffc107; color: #fff;">
|
|
<h5 class="mb-0">6. Alignment Insight</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<h4><span class="badge bg-warning">Noticeable Shift</span></h4>
|
|
<p class="lead"><strong>Risk:</strong> While revenue is up, the drop in visit frequency and recent negative feedback on facilities could impact retention next period.</p>
|
|
<p class="lead"><strong>Opportunity:</strong> High praise for specific instructors suggests promoting them more could boost attendance.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Overall Status Banner -->
|
|
<div class="alert alert-warning mt-4 text-center" role="alert">
|
|
<strong>CAUTION:</strong> Visits Dropped 10%
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Drill-down Modal -->
|
|
<div class="modal fade" id="drilldownModal" tabindex="-1" aria-labelledby="drilldownModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-xl">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="drilldownModalLabel">Drill-down</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<table id="drilldownTable" class="table table-striped table-bordered">
|
|
<thead>
|
|
</thead>
|
|
<tbody>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
|
<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
|
|
<script src="https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap5.min.js"></script>
|
|
<script src="https://cdn.datatables.net/buttons/2.4.2/js/dataTables.buttons.min.js"></script>
|
|
<script src="https://cdn.datatables.net/buttons/2.4.2/js/buttons.bootstrap5.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
|
|
<script src="https://cdn.datatables.net/buttons/2.4.2/js/buttons.html5.min.js"></script>
|
|
<script>
|
|
$(document).ready(function() {
|
|
let community_id = 1; // Default community
|
|
let start_date = '2025-12-01'; // Default start date
|
|
let end_date = '2025-12-31'; // Default end date
|
|
|
|
function updateKPIs() {
|
|
$.getJSON(`api.php?kpi=unique_members&community_id=${community_id}&start_date=${start_date}&end_date=${end_date}`, function(data) {
|
|
$('#unique_members_kpi').text(data.unique_members);
|
|
});
|
|
$.getJSON(`api.php?kpi=avg_class_attendance&community_id=${community_id}&start_date=${start_date}&end_date=${end_date}`, function(data) {
|
|
$('#avg_class_attendance_kpi').text(data.avg_class_attendance);
|
|
});
|
|
$.getJSON(`api.php?kpi=classes_lightly_attended&community_id=${community_id}&start_date=${start_date}&end_date=${end_date}`, function(data) {
|
|
$('#classes_lightly_attended_kpi').text(data.classes_lightly_attended + '%');
|
|
});
|
|
$.getJSON(`api.php?kpi=attendance_trend&community_id=${community_id}&start_date=${start_date}&end_date=${end_date}`, function(data) {
|
|
let trend_html = '';
|
|
if (data.attendance_trend > 0) {
|
|
trend_html = `<span class="text-success">+${data.attendance_trend}% <i class="bi bi-arrow-up-right"></i></span>`;
|
|
} else if (data.attendance_trend < 0) {
|
|
trend_html = `<span class="text-danger">${data.attendance_trend}% <i class="bi bi-arrow-down-right"></i></span>`;
|
|
} else {
|
|
trend_html = `<span>${data.attendance_trend}%</span>`;
|
|
}
|
|
$('#attendance_trend_kpi').html(trend_html);
|
|
});
|
|
$.getJSON(`api.php?kpi=avg_visits_per_member&community_id=${community_id}&start_date=${start_date}&end_date=${end_date}`, function(data) {
|
|
$('#avg_visits_per_member_kpi').text(data.avg_visits_per_member);
|
|
});
|
|
$.getJSON(`api.php?kpi=high_frequency_members&community_id=${community_id}&start_date=${start_date}&end_date=${end_date}`, function(data) {
|
|
$('#high_frequency_members_kpi').text(data.high_frequency_members + '%');
|
|
});
|
|
$.getJSON(`api.php?kpi=total_revenue&community_id=${community_id}&start_date=${start_date}&end_date=${end_date}`, function(data) {
|
|
$('#total_revenue_kpi').text('$' + data.total_revenue);
|
|
});
|
|
$.getJSON(`api.php?kpi=revenue_per_active_member&community_id=${community_id}&start_date=${start_date}&end_date=${end_date}`, function(data) {
|
|
$('#revenue_per_active_member_kpi').text('$' + data.revenue_per_active_member);
|
|
});
|
|
$.getJSON(`api.php?kpi=inactive_members&community_id=${community_id}&start_date=${start_date}&end_date=${end_date}`, function(data) {
|
|
$('#inactive_members_kpi').text(data.inactive_members + '%');
|
|
});
|
|
$.getJSON(`api.php?kpi=visits_dropped&community_id=${community_id}&start_date=${start_date}&end_date=${end_date}`, function(data) {
|
|
let trend_html = '';
|
|
if (data.visits_dropped > 0) {
|
|
trend_html = `<span class="text-success">+${data.visits_dropped}% <i class="bi bi-arrow-up-right"></i></span>`;
|
|
} else if (data.visits_dropped < 0) {
|
|
trend_html = `<span class="text-danger">${data.visits_dropped}% <i class="bi bi-arrow-down-right"></i></span>`;
|
|
} else {
|
|
trend_html = `<span>${data.visits_dropped}%</span>`;
|
|
}
|
|
$('#visits_dropped_kpi').html(trend_html);
|
|
});
|
|
|
|
let table;
|
|
|
|
function openDrilldown(kpi, title) {
|
|
$('#drilldownModalLabel').text(title);
|
|
if (table) {
|
|
table.destroy();
|
|
}
|
|
|
|
$.getJSON(`api.php?kpi=${kpi}_drilldown&community_id=${community_id}&start_date=${start_date}&end_date=${end_date}`, function(data) {
|
|
let columns = [];
|
|
if (data.length > 0) {
|
|
columns = Object.keys(data[0]).map(function(key) {
|
|
return { title: key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()), data: key };
|
|
});
|
|
}
|
|
|
|
table = $('#drilldownTable').DataTable({
|
|
data: data,
|
|
columns: columns,
|
|
dom: 'Bfrtip',
|
|
buttons: [
|
|
'csv'
|
|
]
|
|
});
|
|
|
|
$('#drilldownModal').modal('show');
|
|
});
|
|
}
|
|
|
|
$('.kpi-container').on('click', function() {
|
|
const kpi = $(this).data('kpi');
|
|
const title = $(this).data('title');
|
|
openDrilldown(kpi, title);
|
|
});
|
|
|
|
|
|
updateKPIs();
|
|
updateCharts();
|
|
|
|
let attendanceChart;
|
|
let visitFrequencyChart;
|
|
|
|
function updateCharts() {
|
|
$.getJSON(`api.php?kpi=attendance_over_time&community_id=${community_id}&start_date=${start_date}&end_date=${end_date}`, function(data) {
|
|
const ctx = document.getElementById('attendance_chart').getContext('2d');
|
|
if(attendanceChart) {
|
|
attendanceChart.destroy();
|
|
}
|
|
attendanceChart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: data.labels,
|
|
datasets: [{
|
|
label: 'Attendance Over Time',
|
|
data: data.data,
|
|
borderColor: '#0d6efd',
|
|
tension: 0.1
|
|
}]
|
|
}
|
|
});
|
|
});
|
|
|
|
$.getJSON(`api.php?kpi=visit_frequency_distribution&community_id=${community_id}&start_date=${start_date}&end_date=${end_date}`, function(data) {
|
|
const ctx = document.getElementById('visit_frequency_chart').getContext('2d');
|
|
if(visitFrequencyChart) {
|
|
visitFrequencyChart.destroy();
|
|
}
|
|
visitFrequencyChart = new Chart(ctx, {
|
|
type: 'pie',
|
|
data: {
|
|
labels: data.labels,
|
|
datasets: [{
|
|
label: 'Visit Frequency Distribution',
|
|
data: data.data,
|
|
backgroundColor: [
|
|
'#0d6efd',
|
|
'#198754',
|
|
'#ffc107',
|
|
'#dc3545'
|
|
]
|
|
}]
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
</body> |