8
Dashboard, Analitik, dan Leaderboard
Panel admin menemukan widget secara otomatis, bukan dari daftar manual. AdminPanelProvider memanggil discoverWidgets pada app/Filament/Widgets lalu menutup daftar manual dengan ->widgets([]), sementara halaman analitik masuk lewat discoverPages pada app/Filament/Pages app/Providers/Filament/AdminPanelProvider.php:37-431. Kelas dashboard yang dipakai adalah Filament\Pages\Dashboard bawaan paket app/Providers/Filament/AdminPanelProvider.php:122. Urutan tampil karena itu ditentukan properti $sort di masing-masing kelas, sedangkan isi widget ditentukan peran pembaca.
Widget dashboard dan sumber datanya
graph TD
mdl_ticket["Ticket"]
mdl_user["User"]
mdl_project["Project"]
mdl_history["TicketHistory"]
mdl_comment["TicketComment"]
w_stats["StatsOverview"]
w_trend["MonthlyTicketTrendChart"]
w_projects["TicketsPerProjectChart"]
w_users["UserStatisticsChart"]
w_recent["RecentActivityTable"]
p_board["Leaderboard"]
p_contrib["UserContributions"]
mdl_ticket --> w_stats
mdl_user --> w_stats
mdl_ticket --> w_trend
mdl_project --> w_projects
mdl_ticket --> w_projects
mdl_user --> w_users
mdl_history --> w_recent
mdl_ticket --> p_board
mdl_history --> p_board
mdl_comment --> p_board
mdl_ticket --> p_contrib
mdl_history --> p_contrib
mdl_comment --> p_contrib
style mdl_ticket fill:#336791,color:#fff
style mdl_user fill:#336791,color:#fff
style mdl_project fill:#336791,color:#fff
style mdl_history fill:#336791,color:#fff
style mdl_comment fill:#336791,color:#fff
style w_stats fill:#8b5cf6,color:#fff
style w_trend fill:#8b5cf6,color:#fff
style w_projects fill:#8b5cf6,color:#fff
style w_users fill:#8b5cf6,color:#fff
style w_recent fill:#8b5cf6,color:#fff
style p_board fill:#8b5cf6,color:#fff
style p_contrib fill:#8b5cf6,color:#fff
%% fig: Sumber data setiap widget analitik
%% desc: Widget dan halaman analitik membaca model yang sama; biru menandai penyimpanan dan ungu menandai data turunan yang disajikan ke pembaca.| Widget / halaman | Sumber data | pollingInterval | $sort | Rujukan |
|---|---|---|---|---|
StatsOverview | Project, Ticket, User, pivot ticket_users | 30s | — | app/Filament/Widgets/StatsOverview.php:14-323 |
TicketsPerProjectChart | Project::withCount('tickets') | 30s | 2 | app/Filament/Widgets/TicketsPerProjectChart.php:11-484 |
UserStatisticsChart | User::withCount(['projects', 'assignedTickets']) | 30s | 3 | app/Filament/Widgets/UserStatisticsChart.php:9-375 |
MonthlyTicketTrendChart | Ticket dikelompokkan per tahun dan bulan | 60s | 4 | app/Filament/Widgets/MonthlyTicketTrendChart.php:11-266 |
RecentActivityTable | TicketHistory | 30s lewat ->poll | 7 | app/Filament/Widgets/RecentActivityTable.php:17-287 |
Leaderboard | Ticket, TicketHistory, TicketComment | — | 6 | app/Filament/Pages/Leaderboard.php:17-458 |
UserContributions | Ticket, TicketHistory, TicketComment | — | 5 | app/Filament/Pages/UserContributions.php:18-539 |
Setiap kelas widget memakai trait HasWidgetShield app/Filament/Widgets/UserStatisticsChart.php:9-1310, sehingga izin tampil widget ikut diatur Filament Shield. Pembagian peran dan izinnya dibahas di Kontrol Akses dan Peran.
Dua cabang StatsOverview
StatsOverview memilih cabang di getStats() berdasarkan $user->hasRole('super_admin') app/Filament/Widgets/StatsOverview.php:14-323. Pembaca super_admin mendapat empat stat global: Total Projects, Total Tickets, My Assigned Tickets, dan Team Members app/Filament/Widgets/StatsOverview.php:34-6511. Pembaca lain mendapat delapan stat yang dibatasi ke project dan ticket miliknya, dan sebagian warna berubah mengikuti nilai, misalnya My Assigned Tickets menjadi danger ketika nilainya lebih dari 10 app/Filament/Widgets/StatsOverview.php:108-14912. Dua stat pada cabang itu tidak memakai relasi Eloquent, tetapi query DB::table('tickets') yang di-join ke ticket_users dan ticket_statuses app/Filament/Widgets/StatsOverview.php:67-10713.
Tren ticket bulanan
MonthlyTicketTrendChart adalah line chart berjudul Monthly Ticket Creation Trend dengan $sort = 4, maxHeight 300px, dan pollingInterval 60s app/Filament/Widgets/MonthlyTicketTrendChart.php:11-266. Label sumbu dibangun dari ticket paling awal sampai akhir bulan berjalan app/Filament/Widgets/MonthlyTicketTrendChart.php:44-6114, lalu bulan tanpa ticket diisi 0 lewat $ticketData->get($key)->total ?? 0 app/Filament/Widgets/MonthlyTicketTrendChart.php:96-11615. Query agregasinya sadar driver: pgsql memakai EXTRACT(YEAR FROM created_at), driver lain memakai YEAR(created_at), dan pembaca non super_admin dibatasi whereHas('project.members', ...) app/Filament/Widgets/MonthlyTicketTrendChart.php:68-9016.
Grafik per project dan per user
TicketsPerProjectChart mengandalkan Project::withCount('tickets'); untuk pembaca non super_admin, project dibatasi lewat whereHas('members', ...) app/Filament/Widgets/TicketsPerProjectChart.php:11-484. Warna batang diambil dari sepuluh nilai $baseColors dan diputar dengan modulo $i % count($baseColors) app/Filament/Widgets/TicketsPerProjectChart.php:49-6717. UserStatisticsChart memakai withCount(['projects as total_projects', 'assignedTickets as total_assigned_tickets']); relasi keduanya didefinisikan di model User sebagai projects() lewat pivot project_members dan assignedTickets() lewat pivot ticket_users app/Models/User.php:58-77. Pembaca non super_admin hanya melihat barisnya sendiri karena query dibatasi ->where('id', auth()->id()) app/Filament/Widgets/UserStatisticsChart.php:9-37. Dua dataset grafik ini adalah Total Projects dan Total Assigned Tickets app/Filament/Widgets/UserStatisticsChart.php:39-62.
Aktivitas terbaru
RecentActivityTable membaca TicketHistory::query() dengan eager load ticket.project, user, dan status, diurutkan latest(), dan membatasi pembaca non super_admin lewat whereHas('ticket.project.members', ...) app/Filament/Widgets/RecentActivityTable.php:30-4220. Filter yang tersedia adalah rentang tanggal date_range yang default-nya today(), toggle today, dan SelectFilter untuk user app/Filament/Widgets/RecentActivityTable.php:78-11721. Tabel menyegarkan diri lewat ->poll('30s') dan membatasi halaman pada [5, 25, 50] baris app/Filament/Widgets/RecentActivityTable.php:118-13822.
Skor kontribusi di halaman Leaderboard
graph LR
src_tickets["tickets_created"] -->|"x2"| score["calculateContributionScore"]
src_status["status_changes"] -->|"x5"| score
src_comments["comments_made"] -->|"x2"| score
src_days["active_days"] -->|"x1"| score
score --> total["total_score"]
total --> order["usort descending"]
order --> rank["rank = index + 1"]
rank --> slice["array_slice topCount"]
style src_tickets fill:#336791,color:#fff
style src_status fill:#336791,color:#fff
style src_comments fill:#336791,color:#fff
style src_days fill:#336791,color:#fff
style score fill:#8b5cf6,color:#fff
style total fill:#8b5cf6,color:#fff
style order fill:#f59e0b,color:#000
style slice fill:#2d7d46,color:#fff
%% fig: Perhitungan skor kontribusi Leaderboard
%% desc: Empat stat dikalikan bobotnya dan dijumlahkan menjadi total_score, lalu diurutkan menurun dan dipotong sebanyak topCount.Leaderboard adalah halaman Filament dengan grup navigasi Analytics, navigationSort 6, dan slug leaderboard; nilai awalnya timeRange = '7days' dan topCount = 10 app/Filament/Pages/Leaderboard.php:17-458. getLeaderboardData() mengiterasi User::orderBy('name')->get(), memanggil getUserStats() per user, dan hanya memasukkan user dengan total_score > 0 app/Filament/Pages/Leaderboard.php:47-6423. Hasilnya diurutkan dengan usort menurun, diberi rank = index + 1, lalu dipotong array_slice($leaderboardData, 0, $this->topCount) app/Filament/Pages/Leaderboard.php:65-7824.
| Komponen stat | Bobot | Sumber hitungan | Rujukan |
|---|---|---|---|
tickets_created | 2 | Ticket::where('created_by', $userId) | app/Filament/Pages/Leaderboard.php:80-9625 |
status_changes | 5 | TicketHistory::where('user_id', $userId) | app/Filament/Pages/Leaderboard.php:98-13526 |
comments_made | 2 | TicketComment::where('user_id', $userId) | app/Filament/Pages/Leaderboard.php:98-13526 |
active_days | 1 | gabungan tanggal unik dari tiga sumber di atas | app/Filament/Pages/Leaderboard.php:137-17627 |
Skor akhir adalah jumlah nilai × bobot dari empat komponen tersebut app/Filament/Pages/Leaderboard.php:80-9625. Ketiga hitungan dibatasi whereBetween('created_at', ...) dalam UTC dan dibungkus try/catch; kegagalan dicatat sebagai Log::error('Error getting user stats: ' ...) dan stat dikembalikan 0 app/Filament/Pages/Leaderboard.php:98-13526. active_days dihitung dengan menggabungkan DATE(created_at) yang distinct dari Ticket, TicketHistory, dan TicketComment, lalu unique()->count() app/Filament/Pages/Leaderboard.php:137-17627. Peringkat 1 sampai 3 memakai warna emas, perak, dan perunggu lewat getRankBadgeColor serta ikon getRankIcon app/Filament/Pages/Leaderboard.php:217-23528.
Rentang waktu dipetakan match($this->timeRange): 7days memakai subDays(6), 30days dan 1month memakai subDays(29), thisweek memakai startOfWeek() sampai endOfWeek() app/Filament/Pages/Leaderboard.php:178-20429. Select di view hanya menyediakan 7days dan 30days, dengan pilihan topCount 5, 10, 20, dan 50 resources/views/filament/pages/leaderboard.blade.php:5-2730.
⚠️
getLeaderboardData()memanggilgetUserStats()di dalamforeach ($users as $user), sehingga jumlah query bertambah untuk setiap baris tabelusersapp/Filament/Pages/Leaderboard.php:47-6423. Daftar itu tidak difilter berdasarkan peran; user tanpa aktivitas hanya tersaring karena syarattotal_score > 0.
⚠️ Nilai
thisweekdan1monthtetap dikenaligetDateRangeFromTimeRange()app/Filament/Pages/Leaderboard.php:178-20429, tetapi select di view hanya mengirim7daysatau30daysresources/views/filament/pages/leaderboard.blade.php:5-2730. Kedua nilai itu tidak bisa dipilih dari UI.
Halaman UserContributions
UserContributions memakai slug user-contributions, navigationSort 5, dan grup Analytics; mount() membatasi pembaca non super_admin pada collect([$currentUser]) dengan viewMode = 'individual', sedangkan super_admin mendapat seluruh user dengan viewMode = 'all' app/Filament/Pages/UserContributions.php:18-539. getUsersActivityData() mengambil maksimal sepuluh user ketika viewMode bukan individual; kode menyebut alasannya lewat komentar // Limit to 10 users for performance app/Filament/Pages/UserContributions.php:74-9131.
getUserDailyActivity() mengisi lebih dulu setiap tanggal dalam rentang dengan 0, lalu menjumlahkan ticket baru, perubahan status, dan komentar per hari app/Filament/Pages/UserContributions.php:93-10532. Pengelompokan per hari memakai ->setTimezone(config('app.timezone')) sebelum format('Y-m-d'), sementara batas query-nya memakai UTC app/Filament/Pages/UserContributions.php:152-16433.
timeRange | getDaysFromTimeRange() | Rujukan |
|---|---|---|
1month | 30 | app/Filament/Pages/UserContributions.php:238-24734 |
3months (default) | 90 | app/Filament/Pages/UserContributions.php:238-24734 |
6months | 180 | app/Filament/Pages/UserContributions.php:238-24734 |
1year | 365 | app/Filament/Pages/UserContributions.php:238-24734 |
Label rentang dipetakan terpisah oleh getTimeRangeLabel() menjadi Last Month sampai Last Year, dengan Last 3 Months sebagai nilai default app/Filament/Pages/UserContributions.php:258-26735. Intensitas harian dipetakan getActivityLevel(): 0 menjadi none, 1 sampai 2 menjadi low, 3 sampai 5 menjadi medium, 6 sampai 10 menjadi high, dan di atas 10 menjadi very-high app/Filament/Pages/UserContributions.php:249-25636.
Angka pada halaman ini bersumber dari tabel yang sama dengan Komentar dan Riwayat Ticket, jadi keduanya selalu konsisten setelah perubahan status ticket disimpan.