Dashboard stats
This commit is contained in:
parent
ad56d7a51b
commit
8c5bccae9e
6 changed files with 209 additions and 10 deletions
|
|
@ -67,4 +67,15 @@ public function destroy(Feed $feed): RedirectResponse
|
||||||
return redirect()->route('feeds.index')
|
return redirect()->route('feeds.index')
|
||||||
->with('success', 'Feed deleted successfully!');
|
->with('success', 'Feed deleted successfully!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toggle(Feed $feed): RedirectResponse
|
||||||
|
{
|
||||||
|
$newStatus = !$feed->is_active;
|
||||||
|
$feed->update(['is_active' => $newStatus]);
|
||||||
|
|
||||||
|
$status = $newStatus ? 'activated' : 'deactivated';
|
||||||
|
|
||||||
|
return redirect()->route('feeds.index')
|
||||||
|
->with('success', "Feed {$status} successfully!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,32 @@
|
||||||
use App\Models\PlatformChannel;
|
use App\Models\PlatformChannel;
|
||||||
use App\Models\PlatformInstance;
|
use App\Models\PlatformInstance;
|
||||||
use App\Services\SystemStatusService;
|
use App\Services\SystemStatusService;
|
||||||
|
use App\Services\DashboardStatsService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\View\View;
|
use Illuminate\View\View;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
|
||||||
class OnboardingController extends Controller
|
class OnboardingController extends Controller
|
||||||
{
|
{
|
||||||
public function index(): View|RedirectResponse
|
public function index(Request $request): View|RedirectResponse
|
||||||
{
|
{
|
||||||
// Check if user needs onboarding
|
// Check if user needs onboarding
|
||||||
if (!$this->needsOnboarding()) {
|
if (!$this->needsOnboarding()) {
|
||||||
$systemStatus = resolve(SystemStatusService::class)->getSystemStatus();
|
$systemStatus = resolve(SystemStatusService::class)->getSystemStatus();
|
||||||
|
$statsService = resolve(DashboardStatsService::class);
|
||||||
|
|
||||||
|
$period = $request->get('period', 'today');
|
||||||
|
$stats = $statsService->getStats($period);
|
||||||
|
$systemStats = $statsService->getSystemStats();
|
||||||
|
$availablePeriods = $statsService->getAvailablePeriods();
|
||||||
|
|
||||||
return view('pages.dashboard', compact('systemStatus'));
|
return view('pages.dashboard', compact(
|
||||||
|
'systemStatus',
|
||||||
|
'stats',
|
||||||
|
'systemStats',
|
||||||
|
'availablePeriods',
|
||||||
|
'period'
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('onboarding.welcome');
|
return view('onboarding.welcome');
|
||||||
|
|
|
||||||
83
app/Services/DashboardStatsService.php
Normal file
83
app/Services/DashboardStatsService.php
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Models\Article;
|
||||||
|
use App\Models\ArticlePublication;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
|
class DashboardStatsService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return array{articles_fetched: int, articles_published: int, published_percentage: float}
|
||||||
|
*/
|
||||||
|
public function getStats(string $period = 'today'): array
|
||||||
|
{
|
||||||
|
$dateRange = $this->getDateRange($period);
|
||||||
|
|
||||||
|
$articlesFetched = Article::when($dateRange, function ($query) use ($dateRange) {
|
||||||
|
return $query->whereBetween('created_at', $dateRange);
|
||||||
|
})->count();
|
||||||
|
|
||||||
|
$articlesPublished = ArticlePublication::when($dateRange, function ($query) use ($dateRange) {
|
||||||
|
return $query->whereBetween('published_at', $dateRange);
|
||||||
|
})->count();
|
||||||
|
|
||||||
|
$publishedPercentage = $articlesFetched > 0
|
||||||
|
? round(($articlesPublished / $articlesFetched) * 100, 1)
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'articles_fetched' => $articlesFetched,
|
||||||
|
'articles_published' => $articlesPublished,
|
||||||
|
'published_percentage' => $publishedPercentage,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function getAvailablePeriods(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'today' => 'Today',
|
||||||
|
'week' => 'This Week',
|
||||||
|
'month' => 'This Month',
|
||||||
|
'year' => 'This Year',
|
||||||
|
'all' => 'All Time',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{0: Carbon, 1: Carbon}|null
|
||||||
|
*/
|
||||||
|
private function getDateRange(string $period): ?array
|
||||||
|
{
|
||||||
|
$now = Carbon::now();
|
||||||
|
|
||||||
|
return match ($period) {
|
||||||
|
'today' => [$now->copy()->startOfDay(), $now->copy()->endOfDay()],
|
||||||
|
'week' => [$now->copy()->startOfWeek(), $now->copy()->endOfWeek()],
|
||||||
|
'month' => [$now->copy()->startOfMonth(), $now->copy()->endOfMonth()],
|
||||||
|
'year' => [$now->copy()->startOfYear(), $now->copy()->endOfYear()],
|
||||||
|
'all' => null, // No date filtering for all-time stats
|
||||||
|
default => [$now->copy()->startOfDay(), $now->copy()->endOfDay()],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get additional stats for dashboard
|
||||||
|
* @return array{total_feeds: int, active_feeds: int, total_channels: int, active_channels: int, total_routes: int, active_routes: int}
|
||||||
|
*/
|
||||||
|
public function getSystemStats(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'total_feeds' => \App\Models\Feed::count(),
|
||||||
|
'active_feeds' => \App\Models\Feed::where('is_active', true)->count(),
|
||||||
|
'total_channels' => \App\Models\PlatformChannel::count(),
|
||||||
|
'active_channels' => \App\Models\PlatformChannel::where('is_active', true)->count(),
|
||||||
|
'total_routes' => \App\Models\Route::count(),
|
||||||
|
'active_routes' => \App\Models\Route::where('is_active', true)->count(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,19 @@
|
||||||
<div class="px-4 py-6 sm:px-0">
|
<div class="px-4 py-6 sm:px-0">
|
||||||
<div class="flex justify-between items-center mb-6">
|
<div class="flex justify-between items-center mb-6">
|
||||||
<h1 class="text-3xl font-bold text-gray-900">Dashboard</h1>
|
<h1 class="text-3xl font-bold text-gray-900">Dashboard</h1>
|
||||||
|
|
||||||
|
<!-- Time Period Selector -->
|
||||||
|
<form method="GET" class="flex items-center space-x-2">
|
||||||
|
<label for="period" class="text-sm font-medium text-gray-700">Period:</label>
|
||||||
|
<select name="period" id="period" onchange="this.form.submit()"
|
||||||
|
class="border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 text-sm">
|
||||||
|
@foreach($availablePeriods as $key => $label)
|
||||||
|
<option value="{{ $key }}" {{ $period === $key ? 'selected' : '' }}>
|
||||||
|
{{ $label }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(session('success'))
|
@if(session('success'))
|
||||||
|
|
@ -13,6 +26,60 @@
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
<!-- Article Stats -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
||||||
|
<!-- Articles Fetched -->
|
||||||
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||||
|
<div class="p-5">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<x-heroicon-o-arrow-down-tray class="w-6 h-6 text-blue-600" />
|
||||||
|
</div>
|
||||||
|
<div class="ml-5 w-0 flex-1">
|
||||||
|
<dl>
|
||||||
|
<dt class="text-sm font-medium text-gray-500 truncate">Articles Fetched</dt>
|
||||||
|
<dd class="text-lg font-medium text-gray-900">{{ number_format($stats['articles_fetched']) }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Articles Published -->
|
||||||
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||||
|
<div class="p-5">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<x-heroicon-o-paper-airplane class="w-6 h-6 text-green-600" />
|
||||||
|
</div>
|
||||||
|
<div class="ml-5 w-0 flex-1">
|
||||||
|
<dl>
|
||||||
|
<dt class="text-sm font-medium text-gray-500 truncate">Articles Published</dt>
|
||||||
|
<dd class="text-lg font-medium text-gray-900">{{ number_format($stats['articles_published']) }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Published Percentage -->
|
||||||
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||||
|
<div class="p-5">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<x-heroicon-o-chart-pie class="w-6 h-6 text-purple-600" />
|
||||||
|
</div>
|
||||||
|
<div class="ml-5 w-0 flex-1">
|
||||||
|
<dl>
|
||||||
|
<dt class="text-sm font-medium text-gray-500 truncate">Published Rate</dt>
|
||||||
|
<dd class="text-lg font-medium text-gray-900">{{ $stats['published_percentage'] }}%</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
<!-- System Status Card -->
|
<!-- System Status Card -->
|
||||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||||
|
|
@ -70,18 +137,30 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Quick Stats -->
|
<!-- System Configuration -->
|
||||||
<div class="bg-white overflow-hidden shadow rounded-lg">
|
<div class="bg-white overflow-hidden shadow rounded-lg">
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center mb-4">
|
||||||
<div class="flex-shrink-0">
|
<div class="flex-shrink-0">
|
||||||
<x-heroicon-o-chart-bar class="w-6 h-6 text-gray-400" />
|
<x-heroicon-o-chart-bar class="w-6 h-6 text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-5 w-0 flex-1">
|
<div class="ml-5">
|
||||||
<dl>
|
<h3 class="text-sm font-medium text-gray-500">System Configuration</h3>
|
||||||
<dt class="text-sm font-medium text-gray-500 truncate">Quick Stats</dt>
|
</div>
|
||||||
<dd class="text-lg font-medium text-gray-900">Coming Soon</dd>
|
</div>
|
||||||
</dl>
|
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-sm text-gray-600">Feeds</span>
|
||||||
|
<span class="text-sm font-medium text-gray-900">{{ $systemStats['active_feeds'] }}/{{ $systemStats['total_feeds'] }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-sm text-gray-600">Channels</span>
|
||||||
|
<span class="text-sm font-medium text-gray-900">{{ $systemStats['active_channels'] }}/{{ $systemStats['total_channels'] }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<span class="text-sm text-gray-600">Routes</span>
|
||||||
|
<span class="text-sm font-medium text-gray-900">{{ $systemStats['active_routes'] }}/{{ $systemStats['total_routes'] }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,19 @@
|
||||||
<div class="text-xs text-gray-400 mt-1">{{ $feed->status }}</div>
|
<div class="text-xs text-gray-400 mt-1">{{ $feed->status }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-3">
|
||||||
|
<!-- Toggle Switch -->
|
||||||
|
<form action="{{ route('feeds.toggle', $feed) }}" method="POST" class="inline">
|
||||||
|
@csrf
|
||||||
|
<label class="inline-flex items-center cursor-pointer">
|
||||||
|
<span class="text-xs text-gray-600 mr-2">{{ $feed->is_active ? 'Active' : 'Inactive' }}</span>
|
||||||
|
<button type="submit" class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors {{ $feed->is_active ? 'bg-green-600' : 'bg-gray-200' }}">
|
||||||
|
<span class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform {{ $feed->is_active ? 'translate-x-6' : 'translate-x-1' }}"></span>
|
||||||
|
</button>
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Action Buttons -->
|
||||||
<a href="{{ route('feeds.show', $feed) }}" class="text-indigo-600 hover:text-indigo-900 text-sm font-medium">
|
<a href="{{ route('feeds.show', $feed) }}" class="text-indigo-600 hover:text-indigo-900 text-sm font-medium">
|
||||||
View
|
View
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
Route::resource('channels', App\Http\Controllers\PlatformChannelsController::class)->names('channels');
|
Route::resource('channels', App\Http\Controllers\PlatformChannelsController::class)->names('channels');
|
||||||
Route::post('/channels/{channel}/toggle', [App\Http\Controllers\PlatformChannelsController::class, 'toggle'])->name('channels.toggle');
|
Route::post('/channels/{channel}/toggle', [App\Http\Controllers\PlatformChannelsController::class, 'toggle'])->name('channels.toggle');
|
||||||
Route::resource('feeds', App\Http\Controllers\FeedsController::class)->names('feeds');
|
Route::resource('feeds', App\Http\Controllers\FeedsController::class)->names('feeds');
|
||||||
|
Route::post('/feeds/{feed}/toggle', [App\Http\Controllers\FeedsController::class, 'toggle'])->name('feeds.toggle');
|
||||||
|
|
||||||
Route::get('/routing', [App\Http\Controllers\RoutingController::class, 'index'])->name('routing.index');
|
Route::get('/routing', [App\Http\Controllers\RoutingController::class, 'index'])->name('routing.index');
|
||||||
Route::get('/routing/create', [App\Http\Controllers\RoutingController::class, 'create'])->name('routing.create');
|
Route::get('/routing/create', [App\Http\Controllers\RoutingController::class, 'create'])->name('routing.create');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue