Lompat ke konten
dokumentasi.id
Commit a2522f5
v1
Star645

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

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-43. Kelas dashboard yang dipakai adalah Filament\Pages\Dashboard bawaan paket app/Providers/Filament/AdminPanelProvider.php:12. 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.
Fig. 1: Sumber data setiap widget analitikWidget dan halaman analitik membaca model yang sama; biru menandai penyimpanan dan ungu menandai data turunan yang disajikan ke pembaca.
Widget / halamanSumber datapollingInterval$sortRujukan
StatsOverviewProject, Ticket, User, pivot ticket_users30s—app/Filament/Widgets/StatsOverview.php:14-32
TicketsPerProjectChartProject::withCount('tickets')30s2app/Filament/Widgets/TicketsPerProjectChart.php:11-48
UserStatisticsChartUser::withCount(['projects', 'assignedTickets'])30s3app/Filament/Widgets/UserStatisticsChart.php:9-37
MonthlyTicketTrendChartTicket dikelompokkan per tahun dan bulan60s4app/Filament/Widgets/MonthlyTicketTrendChart.php:11-26
RecentActivityTableTicketHistory30s lewat ->poll7app/Filament/Widgets/RecentActivityTable.php:17-28
LeaderboardTicket, TicketHistory, TicketComment—6app/Filament/Pages/Leaderboard.php:17-45
UserContributionsTicket, TicketHistory, TicketComment—5app/Filament/Pages/UserContributions.php:18-53

Setiap kelas widget memakai trait HasWidgetShield app/Filament/Widgets/UserStatisticsChart.php:9-13, 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-32. Pembaca super_admin mendapat empat stat global: Total Projects, Total Tickets, My Assigned Tickets, dan Team Members app/Filament/Widgets/StatsOverview.php:34-65. 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-149. 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-107.

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-26. Label sumbu dibangun dari ticket paling awal sampai akhir bulan berjalan app/Filament/Widgets/MonthlyTicketTrendChart.php:44-61, lalu bulan tanpa ticket diisi 0 lewat $ticketData->get($key)->total ?? 0 app/Filament/Widgets/MonthlyTicketTrendChart.php:96-116. 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-90.

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-48. Warna batang diambil dari sepuluh nilai $baseColors dan diputar dengan modulo $i % count($baseColors) app/Filament/Widgets/TicketsPerProjectChart.php:49-67. 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-42. Filter yang tersedia adalah rentang tanggal date_range yang default-nya today(), toggle today, dan SelectFilter untuk user app/Filament/Widgets/RecentActivityTable.php:78-117. Tabel menyegarkan diri lewat ->poll('30s') dan membatasi halaman pada [5, 25, 50] baris app/Filament/Widgets/RecentActivityTable.php:118-138.

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.
Fig. 2: Perhitungan skor kontribusi LeaderboardEmpat 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-45. getLeaderboardData() mengiterasi User::orderBy('name')->get(), memanggil getUserStats() per user, dan hanya memasukkan user dengan total_score > 0 app/Filament/Pages/Leaderboard.php:47-64. Hasilnya diurutkan dengan usort menurun, diberi rank = index + 1, lalu dipotong array_slice($leaderboardData, 0, $this->topCount) app/Filament/Pages/Leaderboard.php:65-78.

Komponen statBobotSumber hitunganRujukan
tickets_created2Ticket::where('created_by', $userId)app/Filament/Pages/Leaderboard.php:80-96
status_changes5TicketHistory::where('user_id', $userId)app/Filament/Pages/Leaderboard.php:98-135
comments_made2TicketComment::where('user_id', $userId)app/Filament/Pages/Leaderboard.php:98-135
active_days1gabungan tanggal unik dari tiga sumber di atasapp/Filament/Pages/Leaderboard.php:137-176

Skor akhir adalah jumlah nilai × bobot dari empat komponen tersebut app/Filament/Pages/Leaderboard.php:80-96. 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-135. active_days dihitung dengan menggabungkan DATE(created_at) yang distinct dari Ticket, TicketHistory, dan TicketComment, lalu unique()->count() app/Filament/Pages/Leaderboard.php:137-176. Peringkat 1 sampai 3 memakai warna emas, perak, dan perunggu lewat getRankBadgeColor serta ikon getRankIcon app/Filament/Pages/Leaderboard.php:217-235.

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-204. Select di view hanya menyediakan 7days dan 30days, dengan pilihan topCount 5, 10, 20, dan 50 resources/views/filament/pages/leaderboard.blade.php:5-27.

⚠️ getLeaderboardData() memanggil getUserStats() di dalam foreach ($users as $user), sehingga jumlah query bertambah untuk setiap baris tabel users app/Filament/Pages/Leaderboard.php:47-64. Daftar itu tidak difilter berdasarkan peran; user tanpa aktivitas hanya tersaring karena syarat total_score > 0.

⚠️ Nilai thisweek dan 1month tetap dikenali getDateRangeFromTimeRange() app/Filament/Pages/Leaderboard.php:178-204, tetapi select di view hanya mengirim 7days atau 30days resources/views/filament/pages/leaderboard.blade.php:5-27. 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-53. 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-91.

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-105. 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-164.

timeRangegetDaysFromTimeRange()Rujukan
1month30app/Filament/Pages/UserContributions.php:238-247
3months (default)90app/Filament/Pages/UserContributions.php:238-247
6months180app/Filament/Pages/UserContributions.php:238-247
1year365app/Filament/Pages/UserContributions.php:238-247

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-267. 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-256.

Angka pada halaman ini bersumber dari tabel yang sama dengan Komentar dan Riwayat Ticket, jadi keduanya selalu konsisten setelah perubahan status ticket disimpan.