'error', 'message' => 'Method not allowed' ], JSON_UNESCAPED_UNICODE); exit; } if (!isAdminLoggedIn()) { http_response_code(401); echo json_encode([ 'status' => 'error', 'message' => 'Unauthorized' ], JSON_UNESCAPED_UNICODE); exit; } $range = isset($_GET['days']) ? trim((string)$_GET['days']) : '7'; $allowedRanges = ['7', '30', '90', 'all', 'this_year', 'last_year']; if (!in_array($range, $allowedRanges, true)) { $range = '7'; } $whereWindow = ''; $baseParams = []; $now = new DateTimeImmutable('now'); switch ($range) { case 'all': break; case 'this_year': $start = new DateTimeImmutable(date('Y-01-01 00:00:00')); $end = $start->modify('+1 year'); $whereWindow = ' AND created_at >= :start_at AND created_at < :end_at'; $baseParams = [ ':start_at' => $start->format('Y-m-d H:i:s'), ':end_at' => $end->format('Y-m-d H:i:s'), ]; break; case 'last_year': $start = new DateTimeImmutable((date('Y') - 1) . '-01-01 00:00:00'); $end = $start->modify('+1 year'); $whereWindow = ' AND created_at >= :start_at AND created_at < :end_at'; $baseParams = [ ':start_at' => $start->format('Y-m-d H:i:s'), ':end_at' => $end->format('Y-m-d H:i:s'), ]; break; default: $daysInt = (int)$range; if ($daysInt < 1) $daysInt = 1; if ($daysInt > 3650) $daysInt = 3650; $start = $now->modify("-{$daysInt} days"); $whereWindow = ' AND created_at >= :start_at'; $baseParams = [ ':start_at' => $start->format('Y-m-d H:i:s'), ]; break; } try { $pdo = getAnalyticsPdo(); $sqlTopTables = " SELECT COALESCE(NULLIF(table_id, ''), 'unknown') AS table_id, SUM(CASE WHEN event_name = 'qr_scan' THEN 1 ELSE 0 END) AS qr_scans, SUM(CASE WHEN event_name = 'session_start' THEN 1 ELSE 0 END) AS sessions, SUM(CASE WHEN event_name = 'geo_check_passed' THEN 1 ELSE 0 END) AS geo_pass, SUM(CASE WHEN event_name = 'geo_check_failed' THEN 1 ELSE 0 END) AS geo_fail FROM analytics_events WHERE 1=1 {$whereWindow} GROUP BY COALESCE(NULLIF(table_id, ''), 'unknown') ORDER BY qr_scans DESC, sessions DESC LIMIT 20 "; $stmtTopTables = $pdo->prepare($sqlTopTables); $stmtTopTables->execute($baseParams); $topTables = $stmtTopTables->fetchAll(); $sqlByZone = " SELECT COALESCE(NULLIF(zone, ''), 'unknown') AS zone, SUM(CASE WHEN event_name = 'qr_scan' THEN 1 ELSE 0 END) AS qr_scans, SUM(CASE WHEN event_name = 'session_start' THEN 1 ELSE 0 END) AS sessions, SUM(CASE WHEN event_name = 'geo_check_passed' THEN 1 ELSE 0 END) AS geo_pass, SUM(CASE WHEN event_name = 'geo_check_failed' THEN 1 ELSE 0 END) AS geo_fail FROM analytics_events WHERE 1=1 {$whereWindow} GROUP BY COALESCE(NULLIF(zone, ''), 'unknown') ORDER BY sessions DESC, qr_scans DESC "; $stmtByZone = $pdo->prepare($sqlByZone); $stmtByZone->execute($baseParams); $zoneStats = $stmtByZone->fetchAll(); $sqlFunnel = " SELECT event_name, COUNT(*) AS total FROM analytics_events WHERE 1=1 {$whereWindow} AND event_name IN ('qr_scan','session_start','view_menu','bill_dialog_opened','bill_request_sent','waiter_call_requested') GROUP BY event_name "; $stmtFunnel = $pdo->prepare($sqlFunnel); $stmtFunnel->execute($baseParams); $funnelRows = $stmtFunnel->fetchAll(); $funnelMap = []; foreach ($funnelRows as $row) { $funnelMap[$row['event_name']] = (int)$row['total']; } $sqlGeo = " SELECT SUM(CASE WHEN event_name = 'geo_check_passed' THEN 1 ELSE 0 END) AS geo_passed, SUM(CASE WHEN event_name = 'geo_check_failed' THEN 1 ELSE 0 END) AS geo_failed, SUM(CASE WHEN event_name = 'geo_bypass_host' THEN 1 ELSE 0 END) AS geo_bypass FROM analytics_events WHERE 1=1 {$whereWindow} "; $stmtGeo = $pdo->prepare($sqlGeo); $stmtGeo->execute($baseParams); $geo = $stmtGeo->fetch() ?: ['geo_passed' => 0, 'geo_failed' => 0, 'geo_bypass' => 0]; $sqlDeviceStats = " SELECT COALESCE(NULLIF(device_type, ''), 'other') AS device_type, COUNT(*) AS total FROM analytics_events WHERE event_name = 'qr_scan' {$whereWindow} GROUP BY COALESCE(NULLIF(device_type, ''), 'other') "; $stmtDeviceStats = $pdo->prepare($sqlDeviceStats); $stmtDeviceStats->execute($baseParams); $deviceRows = $stmtDeviceStats->fetchAll(); $deviceStats = ['ios' => 0, 'android' => 0, 'other' => 0]; foreach ($deviceRows as $row) { $key = (string) ($row['device_type'] ?? 'other'); if (!isset($deviceStats[$key])) { $deviceStats['other'] += (int) $row['total']; } else { $deviceStats[$key] = (int) $row['total']; } } $sqlRecentOpens = " SELECT created_at, session_id, table_id, zone, device_type, browser, JSON_UNQUOTE(JSON_EXTRACT(payload_json, '$.ipAddress')) AS ip_address FROM analytics_events WHERE event_name = 'qr_scan' {$whereWindow} ORDER BY created_at DESC LIMIT 50 "; $stmtRecentOpens = $pdo->prepare($sqlRecentOpens); $stmtRecentOpens->execute($baseParams); $recentOpens = $stmtRecentOpens ? $stmtRecentOpens->fetchAll() : []; $sessionOutcomes = []; $sessionIds = []; foreach ($recentOpens as $openRow) { $sid = trim((string) ($openRow['session_id'] ?? '')); if ($sid !== '') { $sessionIds[$sid] = true; } } $sessionIds = array_keys($sessionIds); if (!empty($sessionIds)) { $placeholders = implode(',', array_fill(0, count($sessionIds), '?')); $sqlSessionFlags = " SELECT session_id, MAX(CASE WHEN event_name = 'session_start' THEN 1 ELSE 0 END) AS reached_app, MAX(CASE WHEN event_name = 'view_menu' THEN 1 ELSE 0 END) AS entered_menu FROM analytics_events WHERE session_id IN ({$placeholders}) GROUP BY session_id "; $stmtSessionFlags = $pdo->prepare($sqlSessionFlags); $stmtSessionFlags->execute($sessionIds); while ($flagRow = $stmtSessionFlags->fetch()) { $sessionOutcomes[$flagRow['session_id']] = [ 'reached_app' => (int) $flagRow['reached_app'], 'entered_menu' => (int) $flagRow['entered_menu'], ]; } } foreach ($recentOpens as &$openRow) { $sid = trim((string) ($openRow['session_id'] ?? '')); $flags = $sessionOutcomes[$sid] ?? ['reached_app' => 0, 'entered_menu' => 0]; $openRow['reached_app'] = $flags['reached_app']; $openRow['entered_menu'] = $flags['entered_menu']; } unset($openRow); $sqlQueueSummary = " SELECT COUNT(*) AS total_actions, SUM(CASE WHEN api_sent = 0 THEN 1 ELSE 0 END) AS pending_api, SUM(CASE WHEN status_kds = 0 THEN 1 ELSE 0 END) AS pending_kds, SUM(CASE WHEN status_kds = 1 THEN 1 ELSE 0 END) AS done_kds FROM guest_action_queue WHERE 1=1 {$whereWindow} "; $stmtQueueSummary = $pdo->prepare($sqlQueueSummary); $stmtQueueSummary->execute($baseParams); $queueSummary = $stmtQueueSummary->fetch() ?: [ 'total_actions' => 0, 'pending_api' => 0, 'pending_kds' => 0, 'done_kds' => 0, ]; $sqlQueueItems = " SELECT id, table_id, message_type, message_text, otwierajacy_imie, otwierajacy_nazwisko, api_sent, status_kds, created_at FROM guest_action_queue WHERE 1=1 {$whereWindow} ORDER BY created_at DESC LIMIT 100 "; $stmtQueueItems = $pdo->prepare($sqlQueueItems); $stmtQueueItems->execute($baseParams); $queueItems = $stmtQueueItems->fetchAll(); foreach ($queueItems as &$queueRow) { $queueRow['message_text'] = normalizeQueueMessageText((string) ($queueRow['message_text'] ?? '')); } unset($queueRow); echo json_encode([ 'status' => 'success', 'days' => $range, 'topTables' => $topTables, 'zoneStats' => $zoneStats, 'funnel' => [ 'qr_scan' => (int)($funnelMap['qr_scan'] ?? 0), 'session_start' => (int)($funnelMap['session_start'] ?? 0), 'view_menu' => (int)($funnelMap['view_menu'] ?? 0), 'bill_dialog_opened' => (int)($funnelMap['bill_dialog_opened'] ?? 0), 'bill_request_sent' => (int)($funnelMap['bill_request_sent'] ?? 0), 'waiter_call_requested' => (int)($funnelMap['waiter_call_requested'] ?? 0), ], 'geolocation' => [ 'passed' => (int)$geo['geo_passed'], 'failed' => (int)$geo['geo_failed'], 'bypass' => (int)$geo['geo_bypass'], ], 'deviceStats' => $deviceStats, 'recentOpens' => $recentOpens, 'guestQueueSummary' => [ 'total' => (int)$queueSummary['total_actions'], 'pendingApi' => (int)$queueSummary['pending_api'], 'pendingKds' => (int)$queueSummary['pending_kds'], 'doneKds' => (int)$queueSummary['done_kds'], ], 'guestQueue' => $queueItems, ], JSON_UNESCAPED_UNICODE); } catch (Throwable $e) { http_response_code(500); echo json_encode([ 'status' => 'error', 'message' => 'Nie udało się pobrać raportów analitycznych.' ], JSON_UNESCAPED_UNICODE); }