37243-vm/index.php
2026-01-11 01:28:40 +00:00

320 lines
19 KiB
PHP

<?php
require_once 'db/config.php';
try {
$pdo = db();
// Fetch total customers
$stmt_customers = $pdo->query('SELECT COUNT(*) as total FROM customers');
$total_customers = $stmt_customers->fetch()['total'];
// Fetch total bookings
$stmt_bookings = $pdo->query('SELECT COUNT(*) as total FROM bookings');
$total_bookings = $stmt_bookings->fetch()['total'];
// Fetch total revenue
$stmt_revenue = $pdo->query("SELECT SUM(actual_revenue) as total FROM bookings WHERE status = 'completed'");
$total_revenue = $stmt_revenue->fetch()['total'] ?? 0;
// Fetch recent bookings
$stmt_recent_bookings = $pdo->query('SELECT * FROM bookings ORDER BY created_at DESC LIMIT 5');
$recent_bookings = $stmt_recent_bookings->fetchAll();
// Handle API key generation
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['generate_api_key'])) {
$new_key = 'hvac_' . bin2hex(random_bytes(16));
$stmt_insert_key = $pdo->prepare("INSERT INTO api_keys (api_key) VALUES (?)");
$stmt_insert_key->execute([$new_key]);
header("Location: " . $_SERVER['PHP_SELF']);
exit;
}
// Fetch all API keys
$stmt_api_keys = $pdo->query('SELECT * FROM api_keys ORDER BY created_at DESC');
$api_keys = $stmt_api_keys->fetchAll();
// Fetch latest weather data
$default_zip_code = '28202';
$stmt_weather = $pdo->prepare('SELECT * FROM weather WHERE zip_code = ? ORDER BY observation_time DESC LIMIT 1');
$stmt_weather->execute([$default_zip_code]);
$current_weather = $stmt_weather->fetch();
// Fetch chat stats
$today_start = date('Y-m-d 00:00:00');
$stmt_chats_today = $pdo->prepare("SELECT COUNT(*) FROM chat_logs WHERE chat_start_time >= ?");
$stmt_chats_today->execute([$today_start]);
$chats_today_count = $stmt_chats_today->fetchColumn();
$stmt_total_chats = $pdo->query("SELECT COUNT(*) FROM chat_logs");
$total_chats = $stmt_total_chats->fetchColumn();
$stmt_converted_chats = $pdo->query("SELECT COUNT(*) FROM chat_logs WHERE was_converted = 1");
$converted_chats = $stmt_converted_chats->fetchColumn();
$chat_conversion_rate = ($total_chats > 0) ? ($converted_chats / $total_chats) * 100 : 0;
} catch (PDOException $e) {
$error = "Database error: " . $e->getMessage();
}
$project_name = "HVAC Command Center";
$project_description = "Central dashboard for managing your HVAC business operations.";
$page_title = "Dashboard";
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= htmlspecialchars($page_title . ' | ' . $project_name) ?></title>
<meta name="description" content="<?= htmlspecialchars($project_description) ?>">
<!-- Open Graph / Twitter -->
<meta property="og:title" content="<?= htmlspecialchars($project_name) ?>">
<meta property="og:description" content="<?= htmlspecialchars($project_description) ?>">
<meta property="og:image" content="<?= htmlspecialchars($_SERVER['PROJECT_IMAGE_URL'] ?? '') ?>">
<meta name="twitter:card" content="summary_large_image">
<!-- Styles -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@700&family=Roboto:wght@400;500&display=swap" rel="stylesheet">
</head>
<body>
<div class="sidebar">
<div class="sidebar-header">
<h2 class="h5"><i class="fas fa-tachometer-alt me-2"></i><?= htmlspecialchars($project_name) ?></h2>
</div>
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link active" href="index.php"><i class="fas fa-home"></i>Dashboard</a></li>
<li class="nav-item"><a class="nav-link" href="customers.php"><i class="fas fa-users"></i>Customers</a></li>
<li class="nav-item"><a class="nav-link" href="bookings.php"><i class="fas fa-calendar-check"></i>Bookings</a></li>
<li class="nav-item"><a class="nav-link" href="ai-call-logs.php"><i class="fas fa-robot"></i>AI Call Logs</a></li>
<li class="nav-item"><a class="nav-link" href="chat-logs.php"><i class="fas fa-comments"></i>Chat Logs</a></li>
<li class="nav-item"><a class="nav-link" href="call-tracking.php"><i class="fas fa-phone-alt"></i>Call Tracking</a></li>
<li class="nav-item"><a class="nav-link" href="reviews.php"><i class="fas fa-star"></i>Reviews</a></li>
<li class="nav-item"><a class="nav-link" href="calendar.php"><i class="fas fa-calendar-alt"></i>Calendar</a></li>
</ul>
</div>
<div class="main-content">
<header class="d-flex justify-content-between align-items-center mb-4">
<h1><?= htmlspecialchars($page_title) ?></h1>
<button class="btn btn-primary d-md-none" type="button" data-bs-toggle="collapse" data-bs-target=".sidebar">
<i class="fas fa-bars"></i>
</button>
</header>
<?php if (isset($error)): ?>
<div class="alert alert-danger"><i class="fas fa-exclamation-triangle me-2"></i><?= htmlspecialchars($error) ?></div>
<?php else: ?>
<!-- Stat Cards -->
<div class="row g-4 mb-4">
<div class="col-lg-3 col-md-6">
<div class="card stat-card h-100">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted">Total Customers</h6>
<h4 class="fw-bold mb-0"><?= htmlspecialchars($total_customers) ?></h4>
<small class="change-indicator positive"><i class="fas fa-arrow-up"></i> 5% this month</small>
</div>
<div class="icon"><i class="fas fa-users"></i></div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="card stat-card h-100">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted">Total Bookings</h6>
<h4 class="fw-bold mb-0"><?= htmlspecialchars($total_bookings) ?></h4>
<small class="change-indicator positive"><i class="fas fa-arrow-up"></i> 12% this month</small>
</div>
<div class="icon"><i class="fas fa-calendar-check"></i></div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="card stat-card h-100">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted">Chats Today</h6>
<h4 class="fw-bold mb-0"><?= htmlspecialchars($chats_today_count) ?></h4>
<small class="change-indicator positive"><i class="fas fa-arrow-up"></i> <?= number_format($chat_conversion_rate, 0) ?>% conv.</small>
</div>
<div class="icon"><i class="fas fa-comments"></i></div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="card stat-card h-100">
<div class="card-body d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted">Completed Revenue</h6>
<h4 class="fw-bold mb-0">$<?= number_format($total_revenue, 2) ?></h4>
<small class="change-indicator positive"><i class="fas fa-arrow-up"></i> 8% this month</small>
</div>
<div class="icon"><i class="fas fa-dollar-sign"></i></div>
</div>
</div>
</div>
</div>
<div class="row g-4">
<div class="col-lg-5">
<!-- Weather Widget -->
<div class="card mb-4" id="weather-widget">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5 class="card-title mb-0"><i class="fas fa-cloud-sun me-2"></i>Current Weather</h5>
<button id="refresh-weather-btn" class="btn btn-sm btn-outline-info"><i class="fas fa-sync-alt"></i></button>
</div>
<div id="weather-content">
<?php if ($current_weather): ?>
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="mb-0"><?= htmlspecialchars($current_weather['location_name']) ?></h6>
<p class="text-muted mb-0 small">Last updated: <span id="weather-last-updated"><?= htmlspecialchars(date("g:i A", strtotime($current_weather['observation_time']))) ?></span></p>
</div>
<div class="text-end">
<img id="weather-icon" src="https://openweathermap.org/img/wn/<?= htmlspecialchars($current_weather['weather_icon']) ?>@2x.png" alt="Weather icon" style="width: 50px; height: 50px;">
<span id="weather-temp" class="fs-4 fw-bold"><?= round($current_weather['temperature_f']) ?>&deg;F</span>
<p id="weather-feels-like" class="mb-0 text-muted small">Feels like <?= round($current_weather['feels_like_f']) ?>&deg;F</p>
</div>
</div>
<p id="weather-desc" class="mb-0 fst-italic text-center mt-2"><?= htmlspecialchars(ucwords($current_weather['weather_description'])) ?></p>
<div id="weather-alerts"></div>
<?php else: ?>
<p id="weather-placeholder" class="text-muted text-center mb-0">Weather data not available. Click Refresh.</p>
<?php endif; ?>
</div>
<div id="weather-loading" class="text-center" style="display: none;">
<div class="spinner-border text-info" role="status"><span class="visually-hidden">Loading...</span></div>
<p class="mt-2">Fetching latest weather...</p>
</div>
<div id="weather-error" class="alert alert-warning mt-2" style="display: none;"></div>
</div>
</div>
<!-- Weather Map Widget -->
<div class="card mb-4">
<div class="card-header"><h5 class="card-title mb-0"><i class="fas fa-map-marked-alt me-2"></i>Service Area Weather Map</h5></div>
<div class="card-body p-0"><div id="weather-map"></div></div>
<div class="card-footer text-center map-buttons">
<button class="map-btn active" data-layer="temp" onclick="showLayer('temp')">Temp</button>
<button class="map-btn" data-layer="precip" onclick="showLayer('precip')">Precip</button>
<button class="map-btn" data-layer="clouds" onclick="showLayer('clouds')">Clouds</button>
<button class="map-btn" data-layer="wind" onclick="showLayer('wind')">Wind</button>
</div>
</div>
</div>
<div class="col-lg-7">
<!-- Recent Bookings Table -->
<div class="card mb-4">
<div class="card-header"><h5 class="m-0"><i class="fas fa-clock-rotate-left me-2"></i>Recent Bookings</h5></div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead><tr><th>Date</th><th>Customer</th><th>Service</th><th>Urgency</th><th>Status</th><th class="text-end">Est. Revenue</th></tr></thead>
<tbody>
<?php foreach ($recent_bookings as $booking): ?>
<tr>
<td><?= htmlspecialchars(date("M d, Y", strtotime($booking['appointment_date']))) ?></td>
<td><?= htmlspecialchars($booking['customer_name']) ?></td>
<td><?= htmlspecialchars($booking['service_type']) ?></td>
<td><span class="badge bg-<?= strtolower(htmlspecialchars($booking['urgency_level'])) == 'emergency' ? 'danger' : (strtolower(htmlspecialchars($booking['urgency_level'])) == 'urgent' ? 'warning' : 'secondary') ?>"><?= htmlspecialchars(ucfirst($booking['urgency_level'])) ?></span></td>
<td><span class="badge bg-light text-dark border"><?= htmlspecialchars(ucfirst($booking['status'])) ?></span></td>
<td class="text-end fw-bold">$<?= number_format($booking['estimated_revenue'], 2) ?></td>
</tr>
<?php endforeach; ?>
<?php if (empty($recent_bookings)): ?>
<tr><td colspan="6" class="text-center text-muted">No recent bookings found.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- API Key Management -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="m-0"><i class="fas fa-key me-2"></i>API Keys</h5>
<form method="POST" action=""><button type="submit" name="generate_api_key" class="btn btn-primary btn-sm">Generate New Key</button></form>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead><tr><th>API Key</th><th>Status</th><th>Created On</th><th>Actions</th></tr></thead>
<tbody>
<?php foreach ($api_keys as $key): ?>
<tr>
<td><input type="text" readonly class="form-control-plaintext" value="<?= htmlspecialchars($key['api_key']) ?>"></td>
<td><span class="badge bg-<?= $key['is_active'] ? 'success' : 'danger' ?>"><?= $key['is_active'] ? 'Active' : 'Inactive' ?></span></td>
<td><?= htmlspecialchars(date("M d, Y", strtotime($key['created_at']))) ?></td>
<td></td>
</tr>
<?php endforeach; ?>
<?php if (empty($api_keys)): ?>
<tr><td colspan="4" class="text-center text-muted">No API keys found.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="row g-4 mt-1">
<div class="col-12">
<div class="card">
<div class="card-header"><h5 class="card-title mb-0"><i class="fas fa-chart-line me-2"></i>Booking Trends</h5></div>
<div class="card-body"><canvas id="booking-trends-chart"></canvas></div>
</div>
</div>
</div>
<?php endif; ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.2/dist/chart.umd.min.js"></script>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
<script src="assets/js/map.js?v=<?php echo time(); ?>"></script>
<script>
// Basic Chart.js Example
const ctx = document.getElementById('booking-trends-chart').getContext('2d');
const bookingChart = new Chart(ctx, {
type: 'line',
data: {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [{
label: 'Bookings',
data: [65, 59, 80, 81, 56, 55, 40],
fill: true,
backgroundColor: 'rgba(77, 201, 255, 0.2)',
borderColor: '#4dc9ff',
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: { grid: { color: 'rgba(255,255,255,0.1)' } },
y: { grid: { color: 'rgba(255,255,255,0.1)' } }
},
plugins: { legend: { display: false } }
}
});
</script>
</body>
</html>