Merge pull request #8 from truncgil/dashboard
Add dashboard and widgets: Implemented a new Dashboard page with vari…
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user