Webbserver - Love Blomberg

Show sourcecode

The following files exists in this folder. Click to view.

public_html/smartkortet/admin/

index.php
login.php
logout.php
statistik.php

statistik.php

254 lines UTF-8 Windows (CRLF)
<?php

declare(strict_types=1);

require_once 
__DIR__ '/../includes/functions.php';
startAppSession();

require_once 
__DIR__ '/../includes/auth.php';
require_once 
__DIR__ '/../config/database.php';

$admin requireAdminAuth();

$kpi = [
    
'total_spent' => 0.0,
    
'month_spent' => 0.0,
    
'total_starting_balances' => 0.0,
    
'estimated_remaining' => 0.0,
];

$kpi['total_spent'] = (float) db()->query('SELECT COALESCE(SUM(amount), 0) FROM spend_entries')->fetchColumn();
$kpi['month_spent'] = (float) db()->query('SELECT COALESCE(SUM(amount), 0) FROM spend_entries WHERE spent_on BETWEEN DATE_FORMAT(CURDATE(), "%Y-%m-01") AND LAST_DAY(CURDATE())')->fetchColumn();
$kpi['total_starting_balances'] = (float) db()->query('SELECT COALESCE(SUM(starting_balance), 0) FROM user_settings')->fetchColumn();
$kpi['estimated_remaining'] = (float) db()->query('SELECT COALESCE(SUM(t.remaining), 0) FROM (SELECT us.user_id, GREATEST(us.starting_balance - COALESCE(SUM(se.amount), 0), 0) AS remaining FROM user_settings us LEFT JOIN spend_entries se ON se.user_id = us.user_id AND se.spent_on BETWEEN us.period_start AND us.period_end GROUP BY us.user_id, us.starting_balance) t')->fetchColumn();

$restaurantRows db()->query("SELECT COALESCE(NULLIF(TRIM(place_name), ''), 'Okänd') AS label, COUNT(*) AS visits, SUM(amount) AS total FROM spend_entries GROUP BY label ORDER BY visits DESC, total DESC LIMIT 10")->fetchAll();
$topUsers db()->query('SELECT u.name, u.email, COALESCE(SUM(se.amount), 0) AS total_spent FROM users u LEFT JOIN spend_entries se ON se.user_id = u.id GROUP BY u.id, u.name, u.email ORDER BY total_spent DESC LIMIT 10')->fetchAll();
$dailySpend db()->query('SELECT spent_on AS day_key, SUM(amount) AS total FROM spend_entries WHERE spent_on >= DATE_SUB(CURDATE(), INTERVAL 29 DAY) GROUP BY day_key ORDER BY day_key')->fetchAll();
$userGrowth db()->query('SELECT DATE(created_at) AS day_key, COUNT(*) AS total FROM users WHERE DATE(created_at) >= DATE_SUB(CURDATE(), INTERVAL 29 DAY) GROUP BY day_key ORDER BY day_key')->fetchAll();

$restaurantLabels = [];
$restaurantVisits = [];
foreach (
$restaurantRows as $row) {
    
$restaurantLabels[] = $row['label'];
    
$restaurantVisits[] = (int) $row['visits'];
}

$dailySpendMap = [];
foreach (
$dailySpend as $row) {
    
$dailySpendMap[(string) $row['day_key']] = (float) $row['total'];
}

$userGrowthMap = [];
foreach (
$userGrowth as $row) {
    
$userGrowthMap[(string) $row['day_key']] = (int) $row['total'];
}

$dailyLabels = [];
$dailySpendValues = [];
$growthValues = [];

for (
$i 29$i >= 0$i--) {
    
$date = (new DateTimeImmutable('today'))->modify("-{$i} days");
    
$key $date->format('Y-m-d');
    
$dailyLabels[] = $date->format('d/m');
    
$dailySpendValues[] = $dailySpendMap[$key] ?? 0.0;
    
$growthValues[] = $userGrowthMap[$key] ?? 0;
}
?>
<!doctype html>
<html lang="sv">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
    <meta name="theme-color" content="#0b1220">
    <title>Admin Statistik | Matkortet</title>
    <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=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="<?= e(url('assets/css/style.css')) ?>">
</head>
<body data-theme="dark">
    <main class="app-shell">
        <header class="card panel topbar">
            <div>
                <h1>Admin Statistik</h1>
                <p class="subtitle">Överblick för ekonomi, restauranger och användartillväxt</p>
            </div>
            <div class="admin-actions">
                <a class="btn btn-secondary" href="<?= e(url('index.php')) ?>">Till appen</a>
                <a class="btn btn-secondary" href="<?= e(url('database/createdb.php')) ?>">DB Console</a>
                <a class="btn btn-danger" href="<?= e(url('admin/logout.php')) ?>">Logga ut admin</a>
            </div>
        </header>

        <nav class="card panel admin-subnav">
            <a class="tab-link" href="<?= e(url('admin/index.php')) ?>">Dashboard</a>
            <a class="tab-link active" href="<?= e(url('admin/statistik.php')) ?>">Statistik</a>
        </nav>

        <section class="kpi-grid">
            <article class="card kpi">
                <span>Total spendering (all-time)</span>
                <strong><?= e(formatCurrency($kpi['total_spent'])) ?></strong>
            </article>
            <article class="card kpi">
                <span>Spendering denna månad</span>
                <strong><?= e(formatCurrency($kpi['month_spent'])) ?></strong>
            </article>
            <article class="card kpi">
                <span>Totalt startsaldo</span>
                <strong><?= e(formatCurrency($kpi['total_starting_balances'])) ?></strong>
            </article>
            <article class="card kpi">
                <span>Beräknat kvar totalt</span>
                <strong><?= e(formatCurrency($kpi['estimated_remaining'])) ?></strong>
            </article>
        </section>

        <section class="card panel" style="margin-top:16px;">
            <h3>Diagram</h3>
            <div class="chart-grid admin-chart-grid">
                <article class="chart-card">
                    <h4>Dagsvis spendering (30 dagar)</h4>
                    <canvas id="admin-spend-line"></canvas>
                </article>
                <article class="chart-card">
                    <h4>Populäraste restauranger (besök)</h4>
                    <canvas id="admin-restaurants-bar"></canvas>
                </article>
                <article class="chart-card">
                    <h4>Nya användare per dag (30 dagar)</h4>
                    <canvas id="admin-growth-line"></canvas>
                </article>
            </div>
        </section>

        <section class="card panel" style="margin-top:16px;">
            <h3>Topp 10 restauranger</h3>
            <div class="admin-table-wrap">
                <table class="admin-table">
                    <thead>
                        <tr>
                            <th>Restaurang</th>
                            <th>Besök</th>
                            <th>Total spendering</th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php foreach ($restaurantRows as $row): ?>
                            <tr>
                                <td data-label="Restaurang"><?= e($row['label']) ?></td>
                                <td data-label="Besök"><?= e((string) $row['visits']) ?></td>
                                <td data-label="Total spendering"><?= e(formatCurrency((float) $row['total'])) ?></td>
                            </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
        </section>

        <section class="card panel" style="margin-top:16px;">
            <h3>Topp 10 användare (spendering)</h3>
            <div class="admin-table-wrap">
                <table class="admin-table">
                    <thead>
                        <tr>
                            <th>Namn</th>
                            <th>E-post</th>
                            <th>Total spendering</th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php foreach ($topUsers as $u): ?>
                            <tr>
                                <td data-label="Namn"><?= e($u['name']) ?></td>
                                <td data-label="E-post"><?= e($u['email']) ?></td>
                                <td data-label="Total spendering"><?= e(formatCurrency((float) $u['total_spent'])) ?></td>
                            </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
        </section>
    </main>

    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script>
    (() => {
        const palette = ['#14b8a6', '#38bdf8', '#f59e0b', '#ef4444', '#8b5cf6', '#22c55e', '#06b6d4', '#f97316', '#84cc16', '#eab308'];

        const spendCtx = document.getElementById('admin-spend-line');
        if (spendCtx) {
            new Chart(spendCtx, {
                type: 'line',
                data: {
                    labels: <?= json_encode($dailyLabelsJSON_UNESCAPED_UNICODE?>,
                    datasets: [{
                        label: 'kr',
                        data: <?= json_encode($dailySpendValuesJSON_UNESCAPED_UNICODE?>,
                        borderColor: '#38bdf8',
                        backgroundColor: 'rgba(56, 189, 248, 0.16)',
                        fill: true,
                        tension: 0.25,
                        borderWidth: 3
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    scales: { y: { beginAtZero: true } }
                }
            });
        }

        const restCtx = document.getElementById('admin-restaurants-bar');
        if (restCtx) {
            new Chart(restCtx, {
                type: 'bar',
                data: {
                    labels: <?= json_encode($restaurantLabelsJSON_UNESCAPED_UNICODE?>,
                    datasets: [{
                        label: 'Besök',
                        data: <?= json_encode($restaurantVisitsJSON_UNESCAPED_UNICODE?>,
                        backgroundColor: palette,
                        borderWidth: 0
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    scales: { y: { beginAtZero: true } },
                    plugins: { legend: { display: false } }
                }
            });
        }

        const growthCtx = document.getElementById('admin-growth-line');
        if (growthCtx) {
            new Chart(growthCtx, {
                type: 'line',
                data: {
                    labels: <?= json_encode($dailyLabelsJSON_UNESCAPED_UNICODE?>,
                    datasets: [{
                        label: 'Nya användare',
                        data: <?= json_encode($growthValuesJSON_UNESCAPED_UNICODE?>,
                        borderColor: '#22c55e',
                        backgroundColor: 'rgba(34, 197, 94, 0.15)',
                        fill: true,
                        tension: 0.25,
                        borderWidth: 3
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    scales: { y: { beginAtZero: true, precision: 0 } }
                }
            });
        }
    })();
    </script>
</body>
</html>