Add dashboard and widgets: Implemented a new Dashboard page with various widgets including Content Overview, Recent Activities, System Stats, Translation Progress, and User Activity. Added localization support for dashboard elements in English and Turkish.

This commit is contained in:
Ümit Tunç
2025-10-24 16:16:27 -03:00
parent 64a662d22a
commit 1da81a1874
9 changed files with 604 additions and 1 deletions
+63
View File
@@ -0,0 +1,63 @@
<?php
namespace App\Filament\Admin\Pages;
use App\Filament\Admin\Widgets\ContentOverviewWidget;
use App\Filament\Admin\Widgets\RecentActivitiesWidget;
use App\Filament\Admin\Widgets\SystemStatsWidget;
use App\Filament\Admin\Widgets\TranslationProgressWidget;
use App\Filament\Admin\Widgets\UserActivityWidget;
use Filament\Actions\Action;
use Filament\Pages\Dashboard as BaseDashboard;
class Dashboard extends BaseDashboard
{
public function getTitle(): string
{
return __('dashboard.title');
}
public static function getNavigationLabel(): string
{
return __('dashboard.navigation_label');
}
public function getHeaderActions(): array
{
return [
Action::make('create_page')
->label(__('dashboard.actions.create_page'))
->icon('heroicon-m-plus')
->url(route('filament.admin.resources.pages.create'))
->color('primary'),
Action::make('manage_users')
->label(__('dashboard.actions.manage_users'))
->icon('heroicon-m-users')
->url(route('filament.admin.resources.users.index'))
->color('gray'),
];
}
public function getHeaderWidgets(): array
{
return [
SystemStatsWidget::class,
];
}
public function getWidgets(): array
{
return [
RecentActivitiesWidget::class,
TranslationProgressWidget::class,
UserActivityWidget::class,
ContentOverviewWidget::class,
];
}
public function getColumns(): array | int
{
return 1;
}
}
@@ -0,0 +1,89 @@
<?php
namespace App\Filament\Admin\Widgets;
use App\Models\Page;
use App\Models\Translation;
use Filament\Widgets\ChartWidget;
class ContentOverviewWidget extends ChartWidget
{
protected static ?int $sort = 5;
protected int | string | array $columnSpan = 'full';
protected function getData(): array
{
$pagesByStatus = Page::selectRaw('status, count(*) as count')
->groupBy('status')
->pluck('count', 'status')
->toArray();
$translationsByStatus = Translation::selectRaw('status, count(*) as count')
->groupBy('status')
->pluck('count', 'status')
->toArray();
return [
'datasets' => [
[
'label' => __('dashboard.content_overview.pages_by_status'),
'data' => [
$pagesByStatus['published'] ?? 0,
$pagesByStatus['draft'] ?? 0,
$pagesByStatus['review'] ?? 0,
],
'backgroundColor' => [
'rgb(34, 197, 94)', // success - published
'rgb(156, 163, 175)', // gray - draft
'rgb(245, 158, 11)', // warning - review
],
],
[
'label' => __('dashboard.content_overview.translations_by_status'),
'data' => [
$translationsByStatus['published'] ?? 0,
$translationsByStatus['draft'] ?? 0,
$translationsByStatus['review'] ?? 0,
$translationsByStatus['approved'] ?? 0,
],
'backgroundColor' => [
'rgb(34, 197, 94)', // success - published
'rgb(156, 163, 175)', // gray - draft
'rgb(245, 158, 11)', // warning - review
'rgb(59, 130, 246)', // info - approved
],
],
],
'labels' => [
__('dashboard.content_overview.published'),
__('dashboard.content_overview.draft'),
__('dashboard.content_overview.review'),
__('dashboard.content_overview.approved'),
],
];
}
protected function getType(): string
{
return 'bar';
}
protected function getOptions(): array
{
return [
'responsive' => true,
'maintainAspectRatio' => false,
'plugins' => [
'legend' => [
'position' => 'top',
],
],
'scales' => [
'y' => [
'beginAtZero' => true,
],
],
];
}
}
@@ -0,0 +1,52 @@
<?php
namespace App\Filament\Admin\Widgets;
use App\Models\Page;
use App\Models\Translation;
use App\Models\User;
use Filament\Tables;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget as BaseWidget;
class RecentActivitiesWidget extends BaseWidget
{
protected static ?int $sort = 2;
protected int | string | array $columnSpan = 'full';
public function table(Table $table): Table
{
return $table
->query(
Page::query()
->latest()
->limit(5)
)
->columns([
Tables\Columns\TextColumn::make('title')
->label(__('dashboard.activities.recent_pages'))
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('status')
->badge()
->color(fn (string $state): string => match ($state) {
'published' => 'success',
'draft' => 'gray',
'review' => 'warning',
default => 'gray',
}),
Tables\Columns\TextColumn::make('created_at')
->label(__('dashboard.user_activity.created_at'))
->dateTime()
->sortable(),
Tables\Columns\TextColumn::make('author.name')
->label(__('dashboard.activities.creator'))
->sortable(),
])
->heading(__('dashboard.activities.recent_pages'))
->paginated(false);
}
}
@@ -0,0 +1,46 @@
<?php
namespace App\Filament\Admin\Widgets;
use App\Models\Language;
use App\Models\Page;
use App\Models\Translation;
use App\Models\User;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
class SystemStatsWidget extends BaseWidget
{
protected static ?int $sort = 1;
protected function getStats(): array
{
return [
Stat::make(__('dashboard.stats.total_users'), User::count())
->description(__('dashboard.stats.total_users'))
->descriptionIcon('heroicon-m-users')
->color('primary'),
Stat::make(__('dashboard.stats.total_pages'), Page::count())
->description(__('dashboard.stats.published_pages') . ': ' . Page::where('status', 'published')->count())
->descriptionIcon('heroicon-m-document-text')
->color('success'),
Stat::make(__('dashboard.stats.total_translations'), Translation::count())
->description(__('dashboard.stats.published_translations') . ': ' . Translation::where('status', 'published')->count())
->descriptionIcon('heroicon-m-language')
->color('info'),
Stat::make(__('dashboard.stats.active_languages'), Language::where('is_active', true)->count())
->description(__('dashboard.stats.active_languages'))
->descriptionIcon('heroicon-m-globe-alt')
->color('warning'),
];
}
protected function getColumns(): int
{
return 4;
}
}
@@ -0,0 +1,84 @@
<?php
namespace App\Filament\Admin\Widgets;
use App\Models\Language;
use App\Models\Translation;
use Filament\Tables;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget as BaseWidget;
class TranslationProgressWidget extends BaseWidget
{
protected static ?int $sort = 3;
protected int | string | array $columnSpan = 'full';
public function table(Table $table): Table
{
return $table
->query(
Language::query()
->where('is_active', true)
->withCount([
'translations as total_translations',
'translations as published_translations' => function ($query) {
$query->where('status', 'published');
},
'translations as draft_translations' => function ($query) {
$query->where('status', 'draft');
},
])
)
->columns([
Tables\Columns\TextColumn::make('name')
->label(__('dashboard.translation_progress.language'))
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('native_name')
->label(__('dashboard.translation_progress.native_name'))
->searchable(),
Tables\Columns\TextColumn::make('total_translations')
->label(__('dashboard.translation_progress.total_fields'))
->numeric()
->sortable(),
Tables\Columns\TextColumn::make('published_translations')
->label(__('dashboard.translation_progress.translated_fields'))
->numeric()
->sortable(),
Tables\Columns\TextColumn::make('progress_percentage')
->label(__('dashboard.translation_progress.progress_percentage'))
->getStateUsing(function ($record) {
if ($record->total_translations == 0) {
return '0%';
}
$percentage = round(($record->published_translations / $record->total_translations) * 100);
return $percentage . '%';
})
->badge()
->color(function (string $state): string {
$percentage = (int) str_replace('%', '', $state);
if ($percentage >= 80) {
return 'success';
} elseif ($percentage >= 50) {
return 'warning';
} else {
return 'danger';
}
}),
Tables\Columns\IconColumn::make('is_active')
->label(__('dashboard.translation_progress.active'))
->boolean(),
Tables\Columns\IconColumn::make('is_default')
->label(__('dashboard.translation_progress.default'))
->boolean(),
])
->heading(__('dashboard.widgets.translation_progress.title'))
->paginated(false);
}
}
@@ -0,0 +1,55 @@
<?php
namespace App\Filament\Admin\Widgets;
use App\Models\User;
use Filament\Tables;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget as BaseWidget;
class UserActivityWidget extends BaseWidget
{
protected static ?int $sort = 4;
protected int | string | array $columnSpan = 'full';
public function table(Table $table): Table
{
return $table
->query(
User::query()
->with('roles')
->latest()
->limit(10)
)
->columns([
Tables\Columns\TextColumn::make('name')
->label(__('dashboard.user_activity.username'))
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('email')
->label(__('dashboard.user_activity.email'))
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('roles.name')
->label(__('dashboard.user_activity.role'))
->badge()
->color('primary'),
Tables\Columns\TextColumn::make('created_at')
->label(__('dashboard.user_activity.created_at'))
->dateTime()
->sortable(),
Tables\Columns\TextColumn::make('email_verified_at')
->label(__('dashboard.user_activity.email_verified'))
->dateTime()
->sortable()
->placeholder(__('dashboard.user_activity.unverified')),
])
->heading(__('dashboard.widgets.user_activity.title'))
->paginated(false);
}
}
@@ -6,7 +6,7 @@ use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\AuthenticateSession;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Pages\Dashboard;
use App\Filament\Admin\Pages\Dashboard;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Colors\Color;
+107
View File
@@ -0,0 +1,107 @@
<?php
return [
'title' => 'Dashboard',
'navigation_label' => 'Dashboard',
// Widgets
'widgets' => [
'system_stats' => [
'title' => 'System Statistics',
'description' => 'Platform overview',
],
'recent_activities' => [
'title' => 'Recent Activities',
'description' => 'Latest created content',
],
'translation_progress' => [
'title' => 'Translation Status',
'description' => 'Translation progress by language',
],
'user_activity' => [
'title' => 'User Activity',
'description' => 'Active users and roles',
],
'content_overview' => [
'title' => 'Content Overview',
'description' => 'Pages and translation status',
],
],
// Stats labels
'stats' => [
'total_users' => 'Total Users',
'total_pages' => 'Total Pages',
'total_translations' => 'Total Translations',
'active_languages' => 'Active Languages',
'published_pages' => 'Published Pages',
'draft_translations' => 'Draft Translations',
'pending_translations' => 'Pending Translations',
'published_translations' => 'Published Translations',
],
// Activity labels
'activities' => [
'recent_pages' => 'Recent Pages',
'recent_translations' => 'Recent Translations',
'recent_users' => 'Recent Users',
'no_activity' => 'No activity yet',
'view_all' => 'View All',
'creator' => 'Creator',
],
// Translation progress
'translation_progress' => [
'language' => 'Language',
'native_name' => 'Native Name',
'total_fields' => 'Total Fields',
'translated_fields' => 'Translated Fields',
'progress_percentage' => 'Progress',
'status' => 'Status',
'last_updated' => 'Last Updated',
'active' => 'Active',
'default' => 'Default',
],
// User activity
'user_activity' => [
'active_users' => 'Active Users',
'user_roles' => 'User Roles',
'last_login' => 'Last Login',
'created_at' => 'Created At',
'role' => 'Role',
'permissions' => 'Permissions',
'username' => 'Username',
'email' => 'Email',
'email_verified' => 'Email Verified',
'unverified' => 'Unverified',
],
// Content overview
'content_overview' => [
'pages_by_status' => 'Pages by Status',
'translations_by_status' => 'Translations by Status',
'published' => 'Published',
'draft' => 'Draft',
'review' => 'Review',
'approved' => 'Approved',
],
// Messages
'messages' => [
'welcome' => 'Welcome to Citrus Platform!',
'system_healthy' => 'System is running healthy',
'no_data' => 'No data available yet',
'loading' => 'Loading...',
],
// Actions
'actions' => [
'create_page' => 'New Page',
'create_translation' => 'New Translation',
'manage_users' => 'User Management',
'manage_languages' => 'Language Management',
'view_reports' => 'View Reports',
],
];
+107
View File
@@ -0,0 +1,107 @@
<?php
return [
'title' => 'Dashboard',
'navigation_label' => 'Dashboard',
// Widgets
'widgets' => [
'system_stats' => [
'title' => 'Sistem İstatistikleri',
'description' => 'Platform genel durumu',
],
'recent_activities' => [
'title' => 'Son Aktiviteler',
'description' => 'Son oluşturulan içerikler',
],
'translation_progress' => [
'title' => 'Çeviri Durumu',
'description' => 'Dil bazında çeviri ilerlemesi',
],
'user_activity' => [
'title' => 'Kullanıcı Aktivitesi',
'description' => 'Aktif kullanıcılar ve roller',
],
'content_overview' => [
'title' => 'İçerik Özeti',
'description' => 'Sayfa ve çeviri durumu',
],
],
// Stats labels
'stats' => [
'total_users' => 'Toplam Kullanıcı',
'total_pages' => 'Toplam Sayfa',
'total_translations' => 'Toplam Çeviri',
'active_languages' => 'Aktif Dil',
'published_pages' => 'Yayınlanan Sayfa',
'draft_translations' => 'Taslak Çeviri',
'pending_translations' => 'Bekleyen Çeviri',
'published_translations' => 'Yayınlanan Çeviri',
],
// Activity labels
'activities' => [
'recent_pages' => 'Son Sayfalar',
'recent_translations' => 'Son Çeviriler',
'recent_users' => 'Son Kullanıcılar',
'no_activity' => 'Henüz aktivite yok',
'view_all' => 'Tümünü Görüntüle',
'creator' => 'Oluşturan',
],
// Translation progress
'translation_progress' => [
'language' => 'Dil',
'native_name' => 'Yerel Ad',
'total_fields' => 'Toplam Alan',
'translated_fields' => 'Çevrilen Alan',
'progress_percentage' => 'İlerleme',
'status' => 'Durum',
'last_updated' => 'Son Güncelleme',
'active' => 'Aktif',
'default' => 'Varsayılan',
],
// User activity
'user_activity' => [
'active_users' => 'Aktif Kullanıcılar',
'user_roles' => 'Kullanıcı Rolleri',
'last_login' => 'Son Giriş',
'created_at' => 'Oluşturulma',
'role' => 'Rol',
'permissions' => 'Yetkiler',
'username' => 'Kullanıcı Adı',
'email' => 'E-posta',
'email_verified' => 'E-posta Doğrulandı',
'unverified' => 'Doğrulanmamış',
],
// Content overview
'content_overview' => [
'pages_by_status' => 'Sayfa Durumları',
'translations_by_status' => 'Çeviri Durumları',
'published' => 'Yayınlanan',
'draft' => 'Taslak',
'review' => 'İnceleme',
'approved' => 'Onaylanan',
],
// Messages
'messages' => [
'welcome' => 'Citrus Platform\'a hoş geldiniz!',
'system_healthy' => 'Sistem sağlıklı çalışıyor',
'no_data' => 'Henüz veri bulunmuyor',
'loading' => 'Yükleniyor...',
],
// Actions
'actions' => [
'create_page' => 'Yeni Sayfa',
'create_translation' => 'Yeni Çeviri',
'manage_users' => 'Kullanıcı Yönetimi',
'manage_languages' => 'Dil Yönetimi',
'view_reports' => 'Raporları Görüntüle',
],
];