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
/
dondedy
/
admin
/
Viewing: profit_reports.php
<?php include '../components/connect.php'; session_start(); $admin_id = $_SESSION['admin_id'] ?? null; if(!$admin_id){ header('location:admin_login.php'); exit(); } date_default_timezone_set('America/Bogota'); $tz = new DateTimeZone('America/Bogota'); $validRanges = ['today','week','month','quarter','year','custom']; $range = $_GET['range'] ?? 'month'; if(!in_array($range, $validRanges, true)){ $range = 'month'; } $today = new DateTime('today', $tz); $start = clone $today; $end = clone $today; $rangeLabel = ''; $customStartParam = $_GET['start_date'] ?? ''; $customEndParam = $_GET['end_date'] ?? ''; switch($range){ case 'today': $start->setTime(0,0,0); $end->setTime(23,59,59); $rangeLabel = 'Hoy'; break; case 'week': $start = (clone $today)->modify('monday this week'); $end = (clone $start)->modify('sunday this week'); $start->setTime(0,0,0); $end->setTime(23,59,59); $rangeLabel = 'Semana actual'; break; case 'quarter': $month = (int)$today->format('n'); $quarterStartMonth = $month - (($month - 1) % 3); $start = new DateTime($today->format('Y') . '-' . str_pad((string)$quarterStartMonth, 2, '0', STR_PAD_LEFT) . '-01', $tz); $end = (clone $start)->modify('+2 months')->modify('last day of this month'); $start->setTime(0,0,0); $end->setTime(23,59,59); $rangeLabel = 'Trimestre actual'; break; case 'year': $start = new DateTime($today->format('Y') . '-01-01', $tz); $end = new DateTime($today->format('Y') . '-12-31', $tz); $start->setTime(0,0,0); $end->setTime(23,59,59); $rangeLabel = 'Año en curso'; break; case 'custom': $sanitizedStart = preg_match('/^\d{4}-\d{2}-\d{2}$/', $customStartParam) ? $customStartParam : ''; $sanitizedEnd = preg_match('/^\d{4}-\d{2}-\d{2}$/', $customEndParam) ? $customEndParam : ''; if($sanitizedStart === '' || $sanitizedEnd === ''){ $range = 'month'; $start = new DateTime($today->format('Y-m-01'), $tz); $end = (clone $start)->modify('last day of this month'); $rangeLabel = 'Mes actual'; } else { $start = DateTime::createFromFormat('Y-m-d', $sanitizedStart, $tz) ?: new DateTime($sanitizedStart, $tz); $end = DateTime::createFromFormat('Y-m-d', $sanitizedEnd, $tz) ?: new DateTime($sanitizedEnd, $tz); if($start > $end){ [$start, $end] = [$end, $start]; } $start->setTime(0,0,0); $end->setTime(23,59,59); $rangeLabel = 'Rango personalizado'; } break; case 'month': default: $start = new DateTime($today->format('Y-m-01'), $tz); $end = (clone $start)->modify('last day of this month'); $start->setTime(0,0,0); $end->setTime(23,59,59); $rangeLabel = 'Mes actual'; break; } $periodStart = $start->format('Y-m-d H:i:s'); $periodEnd = $end->format('Y-m-d H:i:s'); $summaryDefaults = ['revenue' => 0.0, 'cost' => 0.0, 'orders' => 0, 'items' => 0]; $dineStmt = $conn->prepare("SELECT COALESCE(SUM(doi.unit_price * doi.quantity), 0) AS revenue, COALESCE(SUM(doi.cost_price * doi.quantity), 0) AS cost, COUNT(DISTINCT dio.id) AS orders, COALESCE(SUM(doi.quantity), 0) AS items FROM dine_in_orders dio JOIN dine_in_order_items doi ON doi.order_id = dio.id WHERE dio.payment_status = 'pagada' AND dio.order_status != 'cancelada' AND COALESCE(dio.delivered_at, dio.updated_at, dio.created_at) BETWEEN ? AND ?"); $dineStmt->execute([$periodStart, $periodEnd]); $dineSummary = $dineStmt->fetch(PDO::FETCH_ASSOC) ?: $summaryDefaults; $dineSummary = array_merge($summaryDefaults, array_map('floatval', $dineSummary)); $dineSummary['orders'] = (int)($dineSummary['orders'] ?? 0); $dineSummary['items'] = (int)($dineSummary['items'] ?? 0); $deliveryStmt = $conn->prepare("SELECT COALESCE(SUM(doi.price * doi.quantity), 0) AS revenue, COALESCE(SUM(doi.cost_price * doi.quantity), 0) AS cost, COUNT(DISTINCT del.id) AS orders, COALESCE(SUM(doi.quantity), 0) AS items FROM delivery_orders del JOIN delivery_order_items doi ON doi.order_id = del.id WHERE del.status = 'entregado' AND COALESCE(del.updated_at, del.created_at) BETWEEN ? AND ?"); $deliveryStmt->execute([$periodStart, $periodEnd]); $deliverySummary = $deliveryStmt->fetch(PDO::FETCH_ASSOC) ?: $summaryDefaults; $deliverySummary = array_merge($summaryDefaults, array_map('floatval', $deliverySummary)); $deliverySummary['orders'] = (int)($deliverySummary['orders'] ?? 0); $deliverySummary['items'] = (int)($deliverySummary['items'] ?? 0); $totalRevenue = $dineSummary['revenue'] + $deliverySummary['revenue']; $totalCost = $dineSummary['cost'] + $deliverySummary['cost']; $totalProfit = $totalRevenue - $totalCost; $totalOrders = $dineSummary['orders'] + $deliverySummary['orders']; $totalItems = $dineSummary['items'] + $deliverySummary['items']; $profitMargin = $totalRevenue > 0 ? ($totalProfit / $totalRevenue) * 100 : 0; $avgTicket = $totalOrders > 0 ? $totalRevenue / $totalOrders : 0; $topDineStmt = $conn->prepare("SELECT doi.product_name AS name, SUM(doi.quantity) AS qty, SUM(doi.unit_price * doi.quantity) AS revenue, SUM(doi.cost_price * doi.quantity) AS cost FROM dine_in_orders dio JOIN dine_in_order_items doi ON doi.order_id = dio.id WHERE dio.payment_status = 'pagada' AND dio.order_status != 'cancelada' AND COALESCE(dio.delivered_at, dio.updated_at, dio.created_at) BETWEEN ? AND ? GROUP BY doi.product_name ORDER BY (SUM(doi.unit_price * doi.quantity) - SUM(doi.cost_price * doi.quantity)) DESC LIMIT 5"); $topDineStmt->execute([$periodStart, $periodEnd]); $topDineProducts = $topDineStmt->fetchAll(PDO::FETCH_ASSOC); $topDeliveryStmt = $conn->prepare("SELECT doi.product_name AS name, SUM(doi.quantity) AS qty, SUM(doi.price * doi.quantity) AS revenue, SUM(doi.cost_price * doi.quantity) AS cost FROM delivery_orders del JOIN delivery_order_items doi ON doi.order_id = del.id WHERE del.status = 'entregado' AND COALESCE(del.updated_at, del.created_at) BETWEEN ? AND ? GROUP BY doi.product_name ORDER BY (SUM(doi.price * doi.quantity) - SUM(doi.cost_price * doi.quantity)) DESC LIMIT 5"); $topDeliveryStmt->execute([$periodStart, $periodEnd]); $topDeliveryProducts = $topDeliveryStmt->fetchAll(PDO::FETCH_ASSOC); $trendStmt = $conn->prepare("SELECT day, SUM(revenue) AS revenue, SUM(cost) AS cost FROM ( SELECT DATE(COALESCE(dio.delivered_at, dio.updated_at, dio.created_at)) AS day, SUM(doi.unit_price * doi.quantity) AS revenue, SUM(doi.cost_price * doi.quantity) AS cost FROM dine_in_orders dio JOIN dine_in_order_items doi ON doi.order_id = dio.id WHERE dio.payment_status = 'pagada' AND dio.order_status != 'cancelada' AND COALESCE(dio.delivered_at, dio.updated_at, dio.created_at) BETWEEN ? AND ? GROUP BY day UNION ALL SELECT DATE(COALESCE(del.updated_at, del.created_at)) AS day, SUM(doi.price * doi.quantity) AS revenue, SUM(doi.cost_price * doi.quantity) AS cost FROM delivery_orders del JOIN delivery_order_items doi ON doi.order_id = del.id WHERE del.status = 'entregado' AND COALESCE(del.updated_at, del.created_at) BETWEEN ? AND ? GROUP BY day ) combined GROUP BY day ORDER BY day ASC"); $trendStmt->execute([$periodStart, $periodEnd, $periodStart, $periodEnd]); $trendRows = $trendStmt->fetchAll(PDO::FETCH_ASSOC); $trendMap = []; foreach($trendRows as $row){ $dayKey = $row['day']; $trendMap[$dayKey] = [ 'revenue' => (float)$row['revenue'], 'cost' => (float)$row['cost'], ]; } $chartLabels = []; $chartRevenue = []; $chartCost = []; $chartProfit = []; $cursor = clone $start; while($cursor <= $end){ $dayKey = $cursor->format('Y-m-d'); $displayLabel = $cursor->format('d M'); $chartLabels[] = $displayLabel; $revenueValue = $trendMap[$dayKey]['revenue'] ?? 0.0; $costValue = $trendMap[$dayKey]['cost'] ?? 0.0; $chartRevenue[] = round($revenueValue, 2); $chartCost[] = round($costValue, 2); $chartProfit[] = round($revenueValue - $costValue, 2); $cursor->modify('+1 day'); } $chartLabelsJson = json_encode($chartLabels, JSON_UNESCAPED_UNICODE); $chartRevenueJson = json_encode($chartRevenue, JSON_NUMERIC_CHECK); $chartCostJson = json_encode($chartCost, JSON_NUMERIC_CHECK); $chartProfitJson = json_encode($chartProfit, JSON_NUMERIC_CHECK); $rangeDescription = $start->format('d \d\e M \d\e Y') . ' - ' . $end->format('d \d\e M \d\e Y'); $formStartValue = $start->format('Y-m-d'); $formEndValue = $end->format('Y-m-d'); $quickRanges = [ 'today' => ['label' => 'Hoy', 'icon' => 'fa-sun'], 'week' => ['label' => 'Semana', 'icon' => 'fa-calendar-week'], 'month' => ['label' => 'Mes', 'icon' => 'fa-calendar-alt'], 'quarter' => ['label' => 'Trimestre', 'icon' => 'fa-calendar'], 'year' => ['label' => 'Año', 'icon' => 'fa-chart-line'], ]; $totalProfitPercent = $totalRevenue > 0 ? number_format($profitMargin, 2, ',', '.') . '%' : '0%'; function formatCurrency(float $value): string { return 'COP ' . number_format($value, 0, ',', '.'); } ?> <!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>Reporte de Ganancias | DONDEEDY</title> <link rel="icon" href="../images/favicon.png" 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> body { background:#f5f7fb; } .page-heading { display:flex; flex-direction:column; gap:0.6rem; margin-bottom:1.8rem; } .page-heading h1 { font-size:2.4rem; font-weight:800; color:#111827; display:flex; align-items:center; gap:0.75rem; margin:0; } .page-heading h1 i { color:#b30000; } .page-heading p { margin:0; color:#475569; font-weight:600; } .filters-wrapper { background:#fff; border-radius:18px; padding:1.2rem 1.5rem; box-shadow:0 14px 30px rgba(15,23,42,0.08); margin-bottom:2rem; display:flex; flex-wrap:wrap; gap:1.4rem; align-items:flex-start; } .quick-filters { display:flex; flex-wrap:wrap; gap:0.75rem; } .filter-chip { display:inline-flex; align-items:center; gap:0.55rem; padding:0.65rem 1.1rem; border-radius:999px; border:1px solid rgba(179,0,0,0.18); font-weight:700; color:#b30000; background:#fff; text-decoration:none; transition:all 0.2s ease; } .filter-chip i { font-size:1rem; } .filter-chip:hover { background:#fff5f5; box-shadow:0 10px 18px rgba(179,0,0,0.12); transform:translateY(-1px); } .filter-chip.active { background:linear-gradient(135deg,#b30000,#7a0000); color:#fff; border-color:transparent; box-shadow:0 16px 32px rgba(179,0,0,0.28); } .custom-range-form { display:flex; flex-wrap:wrap; gap:0.75rem; align-items:flex-end; margin-left:auto; } .custom-range-form label { font-weight:700; color:#1f2937; font-size:0.9rem; display:block; margin-bottom:0.35rem; } .custom-range-form input[type="date"] { border:1px solid rgba(15,23,42,0.12); border-radius:12px; padding:0.55rem 0.75rem; font-size:0.95rem; min-width:180px; } .custom-range-form button { border:none; border-radius:12px; padding:0.65rem 1.1rem; background:linear-gradient(135deg,#0ea5e9,#0284c7); color:#fff; font-weight:700; cursor:pointer; box-shadow:0 12px 26px rgba(14,165,233,0.25); transition:transform 0.2s ease; } .custom-range-form button:hover { transform:translateY(-1px); } .summary-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:1.2rem; margin-bottom:2.2rem; } .summary-card { background:#fff; border-radius:18px; padding:1.3rem 1.4rem; box-shadow:0 14px 32px rgba(15,23,42,0.08); border:1px solid rgba(15,23,42,0.05); display:flex; flex-direction:column; gap:0.5rem; position:relative; overflow:hidden; } .summary-card.accent { background:linear-gradient(135deg,#0f766e,#14b8a6); color:#fff; } .summary-card h3 { margin:0; font-size:1rem; font-weight:700; color:inherit; opacity:0.85; display:flex; align-items:center; gap:0.45rem; } .summary-value { font-size:2rem; font-weight:800; color:inherit; display:flex; align-items:center; gap:0.45rem; } .summary-meta { font-size:0.9rem; font-weight:600; color:rgba(17,24,39,0.65); } .summary-card.accent .summary-meta { color:rgba(255,255,255,0.8); } .trend-card { background:#fff; border-radius:20px; padding:1.4rem; box-shadow:0 16px 36px rgba(15,23,42,0.08); border:1px solid rgba(15,23,42,0.04); margin-bottom:2.2rem; } .trend-card header { display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem; } .trend-card h2 { margin:0; font-size:1.4rem; font-weight:800; color:#111827; display:flex; align-items:center; gap:0.55rem; } .chart-container { position:relative; width:100%; height:320px; } .channel-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:1.2rem; margin-bottom:2.2rem; } .channel-card { background:#fff; border-radius:18px; padding:1.3rem 1.4rem; box-shadow:0 12px 28px rgba(15,23,42,0.08); border:1px solid rgba(15,23,42,0.04); display:flex; flex-direction:column; gap:0.85rem; } .channel-card header { display:flex; align-items:center; justify-content:space-between; } .channel-card h3 { margin:0; font-size:1.2rem; font-weight:800; color:#111827; display:flex; align-items:center; gap:0.5rem; } .channel-stats { display:grid; grid-template-columns:repeat(auto-fit,minmax(160px,1fr)); gap:0.8rem; } .stat-block { background:#f8fafc; border-radius:14px; padding:0.85rem 1rem; display:flex; flex-direction:column; gap:0.35rem; border:1px solid rgba(15,23,42,0.05); } .stat-block span { font-size:0.85rem; color:#64748b; font-weight:600; } .stat-block strong { font-size:1.1rem; color:#0f172a; font-weight:800; } .table-section { background:#fff; border-radius:20px; padding:1.4rem; box-shadow:0 14px 32px rgba(15,23,42,0.08); border:1px solid rgba(15,23,42,0.05); margin-bottom:2.4rem; } .table-section h3 { margin:0 0 1rem; font-size:1.35rem; font-weight:800; color:#111827; display:flex; align-items:center; gap:0.55rem; } .table-wrapper { overflow:auto; border-radius:16px; border:1px solid rgba(15,23,42,0.05); } table { width:100%; border-collapse:collapse; } th, td { padding:0.85rem 1rem; text-align:left; font-size:0.95rem; } th { background:#f8fafc; font-weight:700; color:#0f172a; } tr:nth-child(even) td { background:#fbfcff; } td { font-weight:600; color:#1f2937; } .profit-positive { color:#15803d; } .profit-neutral { color:#6b7280; } .empty-state { text-align:center; padding:1.2rem; font-weight:600; color:#6b7280; } @media (max-width: 1024px) { .channel-grid { grid-template-columns:minmax(0,1fr); } .trend-card { padding:1.1rem; } } @media (max-width: 768px) { .filters-wrapper { flex-direction:column; align-items:stretch; gap:1rem; } .quick-filters { width:100%; overflow-x:auto; padding-bottom:0.2rem; } .quick-filters .filter-chip { flex:0 0 auto; } .custom-range-form { margin-left:0; width:100%; justify-content:space-between; } .custom-range-form > div { flex:1 1 45%; } .custom-range-form button { width:100%; } .summary-grid { grid-template-columns:minmax(0,1fr); } .summary-card { padding:1.1rem 1.2rem; } .summary-value { font-size:1.7rem; } .trend-card { margin-bottom:1.8rem; } .chart-container { height:260px; } .channel-grid { gap:1rem; } .channel-card { padding:1.1rem; } .channel-stats { grid-template-columns:minmax(0,1fr); } .custom-range-form { flex-wrap:wrap; row-gap:0.75rem; } .custom-range-form > div { min-width:180px; } } @media (max-width: 560px) { .page-heading h1 { font-size:1.8rem; } .page-heading p { font-size:0.95rem; } .filters-wrapper { padding:1rem; } .quick-filters { gap:0.5rem; } .filter-chip { padding:0.55rem 0.9rem; font-size:0.9rem; } .trend-card header { flex-direction:column; align-items:flex-start; gap:0.45rem; } .summary-card h3 { font-size:0.95rem; } .summary-value { font-size:1.55rem; } } @media (max-width: 420px) { .custom-range-form { flex-direction:column; align-items:stretch; } .custom-range-form > div { width:100%; } .filters-wrapper { gap:0.85rem; } .filter-chip { flex:1 0 auto; text-align:center; } .summary-card { padding:1rem; } .channel-card { padding:1rem; } .channel-card h3 { font-size:1.05rem; } .table-section { padding:1.1rem; } } </style> </head> <body class="admin-panel"> <?php include '../components/admin_header.php'; ?> <section class="page-heading"> <h1><i class="fas fa-sack-dollar"></i>Reporte de ganancias</h1> <p><?= htmlspecialchars($rangeLabel, ENT_QUOTES, 'UTF-8'); ?> · <?= htmlspecialchars($rangeDescription, ENT_QUOTES, 'UTF-8'); ?></p> </section> <section class="filters-wrapper"> <div class="quick-filters"> <?php foreach($quickRanges as $key => $data): ?> <a href="?range=<?= $key; ?>" class="filter-chip <?= $range === $key ? 'active' : ''; ?>"> <i class="fas <?= $data['icon']; ?>"></i><?= htmlspecialchars($data['label'], ENT_QUOTES, 'UTF-8'); ?> </a> <?php endforeach; ?> </div> <form method="get" class="custom-range-form"> <input type="hidden" name="range" value="custom"> <div> <label for="startDate">Desde</label> <input id="startDate" type="date" name="start_date" value="<?= htmlspecialchars($formStartValue, ENT_QUOTES, 'UTF-8'); ?>"> </div> <div> <label for="endDate">Hasta</label> <input id="endDate" type="date" name="end_date" value="<?= htmlspecialchars($formEndValue, ENT_QUOTES, 'UTF-8'); ?>"> </div> <button type="submit"><i class="fas fa-filter"></i> Aplicar</button> </form> </section> <section class="summary-grid"> <article class="summary-card accent"> <h3><i class="fas fa-sack-dollar"></i>Utilidad neta</h3> <div class="summary-value"><?= formatCurrency($totalProfit); ?></div> <span class="summary-meta">Margen promedio: <?= $totalProfitPercent; ?></span> </article> <article class="summary-card"> <h3><i class="fas fa-cash-register"></i>Ingresos totales</h3> <div class="summary-value"><?= formatCurrency($totalRevenue); ?></div> <span class="summary-meta">Antes de costos</span> </article> <article class="summary-card"> <h3><i class="fas fa-wallet"></i>Costos asociados</h3> <div class="summary-value"><?= formatCurrency($totalCost); ?></div> <span class="summary-meta">Costos de preparación/insumos</span> </article> <article class="summary-card"> <h3><i class="fas fa-percent"></i>Margen</h3> <div class="summary-value"><?= $totalProfitPercent; ?></div> <span class="summary-meta">Utilidad sobre ingresos</span> </article> </section> <section class="trend-card"> <header> <h2><i class="fas fa-chart-line"></i>Evolución de utilidades</h2> <span style="color:#64748b;font-weight:600;">Ingresos, costos y utilidad por día</span> </header> <div class="chart-container"> <canvas id="profitTrendChart"></canvas> </div> </section> <section class="channel-grid"> <article class="channel-card"> <header> <h3><i class="fas fa-utensils"></i>Comandas pagadas</h3> </header> <div class="channel-stats"> <div class="stat-block"> <span>Ingresos</span> <strong><?= formatCurrency($dineSummary['revenue']); ?></strong> </div> <div class="stat-block"> <span>Costos</span> <strong><?= formatCurrency($dineSummary['cost']); ?></strong> </div> <div class="stat-block"> <span>Utilidad</span> <strong class="<?= ($dineSummary['revenue'] - $dineSummary['cost']) >= 0 ? 'profit-positive' : 'profit-neutral'; ?>"><?= formatCurrency($dineSummary['revenue'] - $dineSummary['cost']); ?></strong> </div> <div class="stat-block"> <span>Pedidos</span> <strong><?= number_format($dineSummary['orders'], 0, ',', '.'); ?></strong> </div> </div> </article> <article class="channel-card"> <header> <h3><i class="fas fa-motorcycle"></i>Pedidos entregados</h3> </header> <div class="channel-stats"> <div class="stat-block"> <span>Ingresos</span> <strong><?= formatCurrency($deliverySummary['revenue']); ?></strong> </div> <div class="stat-block"> <span>Costos</span> <strong><?= formatCurrency($deliverySummary['cost']); ?></strong> </div> <div class="stat-block"> <span>Utilidad</span> <strong class="<?= ($deliverySummary['revenue'] - $deliverySummary['cost']) >= 0 ? 'profit-positive' : 'profit-neutral'; ?>"><?= formatCurrency($deliverySummary['revenue'] - $deliverySummary['cost']); ?></strong> </div> <div class="stat-block"> <span>Pedidos</span> <strong><?= number_format($deliverySummary['orders'], 0, ',', '.'); ?></strong> </div> </div> </article> </section> <section class="table-section"> <h3><i class="fas fa-fire"></i>Top productos por utilidad - Comandas</h3> <div class="table-wrapper"> <table> <thead> <tr> <th>Producto</th> <th>Cant.</th> <th>Ingresos</th> <th>Utilidad</th> </tr> </thead> <tbody> <?php if(empty($topDineProducts)): ?> <tr><td colspan="4" class="empty-state">Sin datos en el rango seleccionado.</td></tr> <?php else: ?> <?php foreach($topDineProducts as $row): $profit = (float)$row['revenue'] - (float)$row['cost']; ?> <tr> <td><?= htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8'); ?></td> <td><?= number_format((float)$row['qty'], 0, ',', '.'); ?></td> <td><?= formatCurrency((float)$row['revenue']); ?></td> <td class="<?= $profit >= 0 ? 'profit-positive' : 'profit-neutral'; ?>"><?= formatCurrency($profit); ?></td> </tr> <?php endforeach; ?> <?php endif; ?> </tbody> </table> </div> </section> <section class="table-section"> <h3><i class="fas fa-box-open"></i>Top productos por utilidad - Domicilios</h3> <div class="table-wrapper"> <table> <thead> <tr> <th>Producto</th> <th>Cant.</th> <th>Ingresos</th> <th>Utilidad</th> </tr> </thead> <tbody> <?php if(empty($topDeliveryProducts)): ?> <tr><td colspan="4" class="empty-state">Sin datos en el rango seleccionado.</td></tr> <?php else: ?> <?php foreach($topDeliveryProducts as $row): $profit = (float)$row['revenue'] - (float)$row['cost']; ?> <tr> <td><?= htmlspecialchars($row['name'], ENT_QUOTES, 'UTF-8'); ?></td> <td><?= number_format((float)$row['qty'], 0, ',', '.'); ?></td> <td><?= formatCurrency((float)$row['revenue']); ?></td> <td class="<?= $profit >= 0 ? 'profit-positive' : 'profit-neutral'; ?>"><?= formatCurrency($profit); ?></td> </tr> <?php endforeach; ?> <?php endif; ?> </tbody> </table> </div> </section> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script> <script> const chartLabels = <?= $chartLabelsJson; ?>; const chartRevenue = <?= $chartRevenueJson; ?>; const chartCost = <?= $chartCostJson; ?>; const chartProfit = <?= $chartProfitJson; ?>; const ctx = document.getElementById('profitTrendChart'); if (ctx && chartLabels.length > 0) { new Chart(ctx, { type: 'line', data: { labels: chartLabels, datasets: [ { label: 'Ingresos', data: chartRevenue, borderColor: '#14b8a6', backgroundColor: 'rgba(20,184,166,0.12)', borderWidth: 3, tension: 0.35, fill: true, pointRadius: 0 }, { label: 'Costos', data: chartCost, borderColor: '#ef4444', backgroundColor: 'rgba(239,68,68,0.12)', borderWidth: 2, tension: 0.35, fill: true, pointRadius: 0 }, { label: 'Utilidad', data: chartProfit, borderColor: '#f59e0b', backgroundColor: 'rgba(245,158,11,0.1)', borderWidth: 2, tension: 0.35, fill: true, pointRadius: 0 } ] }, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', intersect: false }, plugins: { legend: { display: true, labels: { font: { family: 'Montserrat', weight: '600' } } }, tooltip: { callbacks: { label: (ctx) => { const value = ctx.parsed.y || 0; return ctx.dataset.label + ': COP ' + Number(value).toLocaleString('es-CO'); } } } }, scales: { x: { ticks: { font: { family: 'Montserrat' }, maxRotation: 45, minRotation: 45 } }, y: { ticks: { font: { family: 'Montserrat' }, callback: (value) => 'COP ' + Number(value).toLocaleString('es-CO') } } } } }); } </script> <script src="../js/admin_script.js"></script> </body> </html>
Coded With 💗 by
0x6ick