Files
michaelschiemer/src/Application/Admin/Analytics/AnalyticsController.php
Michael Schiemer 8f3c15ddbb fix(console): comprehensive TUI rendering fixes
- Fix Enter key detection: handle multiple Enter key formats (\n, \r, \r\n)
- Reduce flickering: lower render frequency from 60 FPS to 30 FPS
- Fix menu bar visibility: re-render menu bar after content to prevent overwriting
- Fix content positioning: explicit line positioning for categories and commands
- Fix line shifting: clear lines before writing, control newlines manually
- Limit visible items: prevent overflow with maxVisibleCategories/Commands
- Improve CPU usage: increase sleep interval when no events processed

This fixes:
- Enter key not working for selection
- Strong flickering of the application
- Menu bar not visible or being overwritten
- Top half of selection list not displayed
- Lines being shifted/misaligned
2025-11-10 11:06:07 +01:00

384 lines
13 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Application\Admin\Analytics;
use App\Application\Analytics\Service\AnalyticsDashboardService;
use App\Application\Analytics\Service\AnalyticsRealTimeService;
use App\Application\Analytics\Service\AnalyticsReportService;
use App\Framework\Analytics\AnalyticsCategory;
use App\Framework\Analytics\AnalyticsCollector;
use App\Framework\Analytics\Storage\AnalyticsStorage;
use App\Framework\Attributes\Route;
use App\Framework\Http\Method;
use App\Framework\Http\Status;
use App\Framework\Meta\MetaData;
use App\Framework\Router\AdminRoutes;
use App\Framework\Router\Result\JsonResult;
use App\Framework\Router\Result\ViewResult;
use App\Framework\Admin\Attributes\AdminPage;
use App\Framework\Admin\Attributes\AdminSection;
#[AdminSection(name: 'Analytics', icon: 'chart-bar', order: 4, description: 'Analytics and reporting')]
final readonly class AnalyticsController
{
public function __construct(
private AnalyticsDashboardService $dashboardService,
private AnalyticsStorage $storage,
private AnalyticsReportService $reportService,
private AnalyticsRealTimeService $realTimeService,
private AnalyticsCollector $analyticsCollector,
) {
}
#[AdminPage(title: 'Analytics Dashboard', icon: 'chart-bar', section: 'Analytics', order: 10)]
#[Route(path: '/admin/analytics', method: Method::GET, name: AdminRoutes::ANALYTICS_DASHBOARD)]
public function dashboard(): ViewResult
{
// Use mock data for now since analytics services may not be fully configured
$overview = [
'today_page_views' => 0,
'week_page_views' => 0,
'month_page_views' => 0,
'today_visitors' => 0,
'week_visitors' => 0,
'month_visitors' => 0,
'avg_load_time' => 'N/A',
'bounce_rate' => 'N/A',
'avg_session_duration' => 'N/A',
];
$topPages = [];
$trafficSources = [];
$data = [
'title' => 'Analytics Dashboard',
'today_page_views' => $overview['today_page_views'] ?? 0,
'week_page_views' => $overview['week_page_views'] ?? 0,
'month_page_views' => $overview['month_page_views'] ?? 0,
'today_visitors' => $overview['today_visitors'] ?? 0,
'week_visitors' => $overview['week_visitors'] ?? 0,
'month_visitors' => $overview['month_visitors'] ?? 0,
'avg_load_time' => $overview['avg_load_time'] ?? 'N/A',
'bounce_rate' => $overview['bounce_rate'] ?? 'N/A',
'avg_session_duration' => $overview['avg_session_duration'] ?? 'N/A',
'top_pages' => $topPages,
'traffic_sources' => $trafficSources,
'last_update' => date('Y-m-d H:i:s'),
];
return new ViewResult(
template: 'analytics-dashboard',
metaData: new MetaData('Analytics Dashboard', 'Website Analytics and User Behavior'),
data: $data
);
}
#[Route(path: '/admin/analytics/api/overview', method: Method::GET)]
public function getOverview(): JsonResult
{
try {
$endDate = $_GET['end_date'] ?? date('Y-m-d');
$startDate = $_GET['start_date'] ?? date('Y-m-d', strtotime('-30 days'));
// Calculate days between dates
$days = max(1, (strtotime($endDate) - strtotime($startDate)) / 86400 + 1);
$overview = $this->dashboardService->getOverview($startDate, $endDate);
return new JsonResult([
'success' => true,
'data' => $overview,
'period' => [
'start' => $startDate,
'end' => $endDate,
'days' => (int)$days,
],
]);
} catch (\Exception $e) {
return new JsonResult([
'success' => false,
'error' => $e->getMessage(),
], Status::from(500));
}
}
#[Route(path: '/admin/analytics/api/timeseries', method: Method::GET)]
public function getTimeSeries(): JsonResult
{
try {
$metric = $_GET['metric'] ?? 'page_views';
$period = $_GET['period'] ?? 'day';
$days = (int) ($_GET['days'] ?? 30);
$endDate = date('Y-m-d');
$startDate = date('Y-m-d', strtotime("-{$days} days"));
$timeSeries = $this->storage->getTimeSeries($metric, $startDate, $endDate, $period);
return new JsonResult([
'success' => true,
'data' => $timeSeries,
'metric' => $metric,
'period' => $period,
'range' => [
'start' => $startDate,
'end' => $endDate,
'days' => $days,
],
]);
} catch (\Exception $e) {
return new JsonResult([
'success' => false,
'error' => $e->getMessage(),
], Status::from(500));
}
}
#[Route(path: '/admin/analytics/api/top-pages', method: Method::GET)]
public function getTopPages(): JsonResult
{
try {
$limit = (int) ($_GET['limit'] ?? 10);
$days = (int) ($_GET['days'] ?? 30);
$endDate = date('Y-m-d');
$startDate = date('Y-m-d', strtotime("-{$days} days"));
$topPages = $this->dashboardService->getTopPages($startDate, $endDate, $limit);
return new JsonResult([
'success' => true,
'data' => $topPages,
'limit' => $limit,
'period' => [
'start' => $startDate,
'end' => $endDate,
'days' => $days,
],
]);
} catch (\Exception $e) {
return new JsonResult([
'success' => false,
'error' => $e->getMessage(),
], Status::from(500));
}
}
#[Route(path: '/admin/analytics/api/traffic-sources', method: Method::GET)]
public function getTrafficSources(): JsonResult
{
try {
$days = (int) ($_GET['days'] ?? 30);
$endDate = date('Y-m-d');
$startDate = date('Y-m-d', strtotime("-{$days} days"));
$sources = $this->dashboardService->getTrafficSources($startDate, $endDate);
return new JsonResult([
'success' => true,
'data' => $sources,
'period' => [
'start' => $startDate,
'end' => $endDate,
'days' => $days,
],
]);
} catch (\Exception $e) {
return new JsonResult([
'success' => false,
'error' => $e->getMessage(),
], Status::from(500));
}
}
#[Route(path: '/admin/analytics/api/user-behavior', method: Method::GET)]
public function getUserBehavior(): JsonResult
{
try {
$days = (int) ($_GET['days'] ?? 30);
$endDate = date('Y-m-d');
$startDate = date('Y-m-d', strtotime("-{$days} days"));
$behavior = $this->reportService->getUserBehavior($startDate, $endDate);
return new JsonResult([
'success' => true,
'data' => $behavior,
'period' => [
'start' => $startDate,
'end' => $endDate,
'days' => $days,
],
]);
} catch (\Exception $e) {
return new JsonResult([
'success' => false,
'error' => $e->getMessage(),
], Status::from(500));
}
}
#[Route(path: '/admin/analytics/api/business-metrics', method: Method::GET)]
public function getBusinessMetrics(): JsonResult
{
try {
$days = (int) ($_GET['days'] ?? 30);
$endDate = date('Y-m-d');
$startDate = date('Y-m-d', strtotime("-{$days} days"));
$metrics = $this->reportService->getBusinessMetrics($startDate, $endDate);
return new JsonResult([
'success' => true,
'data' => $metrics,
'period' => [
'start' => $startDate,
'end' => $endDate,
'days' => $days,
],
]);
} catch (\Exception $e) {
return new JsonResult([
'success' => false,
'error' => $e->getMessage(),
], Status::from(500));
}
}
#[Route(path: '/admin/analytics/api/real-time', method: Method::GET)]
public function getRealTimeData(): JsonResult
{
try {
$realTime = $this->realTimeService->getRealTimeData();
return new JsonResult([
'success' => true,
'data' => $realTime,
'timestamp' => time(),
'updated_at' => date('Y-m-d H:i:s'),
]);
} catch (\Exception $e) {
return new JsonResult([
'success' => false,
'error' => $e->getMessage(),
], Status::from(500));
}
}
#[Route(path: '/admin/analytics/api/events', method: Method::GET)]
public function getEvents(): JsonResult
{
try {
$category = $_GET['category'] ?? null;
$limit = (int) ($_GET['limit'] ?? 100);
$offset = (int) ($_GET['offset'] ?? 0);
$days = (int) ($_GET['days'] ?? 7);
$endDate = date('Y-m-d');
$startDate = date('Y-m-d', strtotime("-{$days} days"));
$categoryFilter = $category ? AnalyticsCategory::tryFrom($category) : null;
$events = $this->reportService->getEvents($startDate, $endDate, $categoryFilter, $limit, $offset);
$total = $this->reportService->getEventsCount($startDate, $endDate, $categoryFilter);
return new JsonResult([
'success' => true,
'data' => $events,
'pagination' => [
'limit' => $limit,
'offset' => $offset,
'total' => $total,
'has_more' => ($offset + $limit) < $total,
],
'filter' => [
'category' => $category,
'start' => $startDate,
'end' => $endDate,
],
]);
} catch (\Exception $e) {
return new JsonResult([
'success' => false,
'error' => $e->getMessage(),
], Status::from(500));
}
}
#[Route(path: '/admin/analytics/api/export', method: Method::GET)]
public function exportData(): JsonResult
{
try {
$format = $_GET['format'] ?? 'json';
$category = $_GET['category'] ?? null;
$days = (int) ($_GET['days'] ?? 30);
$endDate = date('Y-m-d');
$startDate = date('Y-m-d', strtotime("-{$days} days"));
$categoryFilter = $category ? AnalyticsCategory::tryFrom($category) : null;
$data = $this->reportService->exportData($startDate, $endDate, $categoryFilter, $format);
return new JsonResult([
'success' => true,
'data' => $data,
'format' => $format,
'exported_at' => date('Y-m-d H:i:s'),
'period' => [
'start' => $startDate,
'end' => $endDate,
'days' => $days,
],
]);
} catch (\Exception $e) {
return new JsonResult([
'success' => false,
'error' => $e->getMessage(),
], Status::from(500));
}
}
#[Route(path: '/admin/analytics/api/track', method: Method::POST)]
public function trackEvent(): JsonResult
{
try {
$action = $_POST['action'] ?? null;
$category = $_POST['category'] ?? 'user_behavior';
$properties = json_decode($_POST['properties'] ?? '{}', true);
if (! $action) {
return new JsonResult([
'success' => false,
'error' => 'Action is required',
], Status::from(400));
}
$categoryEnum = AnalyticsCategory::tryFrom($category);
if (! $categoryEnum) {
return new JsonResult([
'success' => false,
'error' => 'Invalid category',
], Status::from(400));
}
$this->analyticsCollector->trackAction($action, $categoryEnum, $properties);
return new JsonResult([
'success' => true,
'message' => 'Event tracked successfully',
'tracked_at' => date('Y-m-d H:i:s'),
]);
} catch (\Exception $e) {
return new JsonResult([
'success' => false,
'error' => $e->getMessage(),
], Status::from(500));
}
}
}