From b1662e6242d2918d4cd98a24d294ace931565545 Mon Sep 17 00:00:00 2001 From: Sari Date: Mon, 3 Aug 2026 08:56:31 +0700 Subject: [PATCH] polish: halaman Laporan premium full-width (hero gradient, filter cepat, KPI 4 kartu, rekap jenis progress bar + kelas, rincian terbaru) + refactor controller (baseQuery/filters/stats) --- .../Admin/ViolationReportController.php | 115 ++++-- resources/views/reports/violations.blade.php | 343 +++++++++++++++--- 2 files changed, 387 insertions(+), 71 deletions(-) diff --git a/app/Http/Controllers/Admin/ViolationReportController.php b/app/Http/Controllers/Admin/ViolationReportController.php index e095fbd..e71b2c9 100644 --- a/app/Http/Controllers/Admin/ViolationReportController.php +++ b/app/Http/Controllers/Admin/ViolationReportController.php @@ -7,6 +7,7 @@ use App\Models\Classes; use App\Models\Setting; use App\Models\Violation; use Barryvdh\DomPDF\Facade\Pdf; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Request; use Illuminate\View\View; @@ -15,36 +16,31 @@ use Illuminate\View\View; */ class ViolationReportController extends Controller { - public function index(): View + public function index(Request $request): View { $classes = Classes::orderBy('name')->get(); + $filters = $this->filters($request); - $query = Violation::query() - ->when(auth()->user()->isScopedWaliKelas(), fn ($q) => $q->whereHas('student', fn ($qq) => $qq->whereIn('class_id', auth()->user()->homeroomClassIds()))); + $query = $this->baseQuery($filters); + $stats = $this->stats($query); - $totalKasus = (clone $query)->count(); - $totalPoin = (clone $query)->sum('points'); - $siswaTerlibat = (clone $query)->distinct('student_id')->count('student_id'); + $recent = (clone $query) + ->with(['student.class', 'violationType']) + ->orderByDesc('violation_date') + ->limit(20) + ->get(); - return view('reports.violations', compact('classes', 'totalKasus', 'totalPoin', 'siswaTerlibat')); + return view('reports.violations', compact('classes', 'filters', 'stats', 'recent')); } public function pdf(Request $request) { - $data = $request->validate([ - 'date_from' => ['nullable', 'date'], - 'date_to' => ['nullable', 'date', 'after_or_equal:date_from'], - 'class_id' => ['nullable', 'exists:classes,id'], - ]); + $filters = $this->filters($request, validate: true); - $query = Violation::with(['student.class', 'violationType.category', 'handlings']) - ->when($data['date_from'] ?? null, fn ($q) => $q->whereDate('violation_date', '>=', $data['date_from'])) - ->when($data['date_to'] ?? null, fn ($q) => $q->whereDate('violation_date', '<=', $data['date_to'])) - ->when($data['class_id'] ?? null, fn ($q) => $q->whereHas('student', fn ($qq) => $qq->where('class_id', $data['class_id']))) - // Wali kelas murni: hanya kelas yang diwalikan - ->when(auth()->user()->isScopedWaliKelas(), fn ($q) => $q->whereHas('student', fn ($qq) => $qq->whereIn('class_id', auth()->user()->homeroomClassIds()))); - - $violations = $query->orderBy('violation_date')->get(); + $violations = $this->baseQuery($filters) + ->with(['student.class', 'violationType.category', 'handlings']) + ->orderBy('violation_date') + ->get(); $summary = [ 'total_kasus' => $violations->count(), @@ -80,17 +76,84 @@ class ViolationReportController extends Controller 'kepala_sekolah_nip' => Setting::getValue('kepala_sekolah_nip', ''), ]; - $periode = ($data['date_from'] ?? 'Awal').' s/d '.($data['date_to'] ?? 'Sekarang'); - $filterKelas = $data['class_id'] ?? null - ? Classes::find($data['class_id'])?->name - : 'Semua Kelas'; + $periode = ($filters['date_from'] ?? 'Awal').' s/d '.($filters['date_to'] ?? 'Sekarang'); + $filterKelas = $filters['class_id'] ? (Classes::find($filters['class_id'])?->name ?? '-') : 'Semua Kelas'; $pdf = Pdf::loadView('reports.violation-report-pdf', compact( 'violations', 'summary', 'perJenis', 'perKelas', 'school', 'periode', 'filterKelas' ))->setPaper('a4', 'landscape'); - $filename = 'rekap-pelanggaran-'.now()->format('Ymd-His').'.pdf'; + return $pdf->download('rekap-pelanggaran-'.now()->format('Ymd-His').'.pdf'); + } - return $pdf->download($filename); + /** + * Ambil & validasi filter dari request. + * + * @return array{date_from: ?string, date_to: ?string, class_id: ?int} + */ + protected function filters(Request $request, bool $validate = false): array + { + $rules = [ + 'date_from' => ['nullable', 'date'], + 'date_to' => ['nullable', 'date', 'after_or_equal:date_from'], + 'class_id' => ['nullable', 'integer', 'exists:classes,id'], + ]; + + $data = $validate ? $request->validate($rules) : $request->validate($rules); + + return [ + 'date_from' => $data['date_from'] ?? null, + 'date_to' => $data['date_to'] ?? null, + 'class_id' => isset($data['class_id']) ? (int) $data['class_id'] : null, + ]; + } + + /** + * Query dasar pelanggaran: scoping wali kelas + filter periode & kelas. + */ + protected function baseQuery(array $filters): Builder + { + return Violation::query() + ->when($filters['date_from'], fn ($q) => $q->whereDate('violation_date', '>=', $filters['date_from'])) + ->when($filters['date_to'], fn ($q) => $q->whereDate('violation_date', '<=', $filters['date_to'])) + ->when($filters['class_id'], fn ($q) => $q->whereHas('student', fn ($qq) => $qq->where('class_id', $filters['class_id']))) + ->when(auth()->user()->isScopedWaliKelas(), fn ($q) => $q->whereHas('student', fn ($qq) => $qq->whereIn('class_id', auth()->user()->homeroomClassIds()))); + } + + /** + * Statistik ringkas + rekap preview untuk halaman. + */ + protected function stats(Builder $query): array + { + $totalKasus = (clone $query)->count(); + $totalPoin = (clone $query)->sum('points'); + $siswaTerlibat = (clone $query)->distinct('student_id')->count('student_id'); + + $rows = (clone $query) + ->with(['violationType.category', 'student.class']) + ->get(['id', 'student_id', 'violation_type_id', 'points', 'violation_date']); + + $perJenis = $rows->groupBy(fn ($v) => $v->violationType?->name ?? 'Tanpa Jenis') + ->map(fn ($group) => [ + 'jenis' => $group->first()->violationType?->name ?? 'Tanpa Jenis', + 'kategori' => $group->first()->violationType?->category?->name ?? '-', + 'jumlah' => $group->count(), + 'poin' => $group->sum('points'), + ]) + ->sortByDesc('jumlah') + ->values(); + + $perKelas = $rows->groupBy(fn ($v) => $v->student?->class?->name ?? 'Tanpa Kelas') + ->map(fn ($group) => [ + 'kelas' => $group->first()->student?->class?->name ?? 'Tanpa Kelas', + 'jumlah' => $group->count(), + 'poin' => $group->sum('points'), + ]) + ->sortByDesc('jumlah') + ->values(); + + $jenisTerbanyak = $perJenis->first(); + + return compact('totalKasus', 'totalPoin', 'siswaTerlibat', 'perJenis', 'perKelas', 'jenisTerbanyak'); } } diff --git a/resources/views/reports/violations.blade.php b/resources/views/reports/violations.blade.php index 7c130f6..1b34eaa 100644 --- a/resources/views/reports/violations.blade.php +++ b/resources/views/reports/violations.blade.php @@ -3,68 +3,321 @@ @section('title', 'Laporan Pelanggaran') @section('content') -
- {{-- Header --}} -
-

Laporan Rekap Pelanggaran

-

Cetak rekap pelanggaran per periode untuk BK & Kepala Sekolah

+@php $selectedClass = $classes->firstWhere('id', $filters['class_id']); @endphp +
+ + {{-- ===== Hero ===== --}} +
+
+
+
+
+ +
+

Laporan Rekap Pelanggaran

+

+ Rekap pelanggaran per periode untuk BK & Kepala Sekolah — cetak langsung atau unduh PDF. +

+
+ + Periode {{ $filters['date_from'] ?? 'Awal' }} — {{ $filters['date_to'] ?? 'Sekarang' }} + + + {{ $selectedClass?->name ?? 'Semua Kelas' }} + +
+
+
+
+ + +
+
- {{-- Form Filter --}} -
-
-
-
- - + {{-- ===== Filter Bar ===== --}} + +
+
+ +
+ +
-
- - +
+
+ +
+ +
-
- - @foreach ($classes as $class) - + @endforeach +
- -
+
- - - Export Data Excel -
- +
+ + {{-- Quick presets --}} +
+ Cepat: + @foreach ([ + '7 Hari Terakhir' => [now()->subDays(6)->format('Y-m-d'), now()->format('Y-m-d')], + '30 Hari Terakhir' => [now()->subDays(29)->format('Y-m-d'), now()->format('Y-m-d')], + 'Semester Ganjil' => [now()->month >= 7 ? now()->year.'-07-01' : now()->subYear()->year.'-07-01', now()->month >= 7 ? now()->year.'-12-31' : now()->subYear()->year.'-12-31'], + 'Semester Genap' => [now()->month >= 7 ? (now()->year + 1).'-01-01' : now()->year.'-01-01', now()->month >= 7 ? (now()->year + 1).'-06-30' : now()->year.'-06-30'], + 'Tahun Ini' => [now()->year.'-01-01', now()->year.'-12-31'], + ] as $label => [$from, $to]) + + @endforeach + +
+ + + {{-- ===== KPI ===== --}} +
+
+
+
+
+
{{ number_format($stats['totalKasus']) }}
+
Total Kasus
+
+
+ +
+
+
+
+
+
+
+
{{ number_format($stats['totalPoin']) }}
+
Total Poin
+
+
+ +
+
+
+
+
+
+
+
{{ number_format($stats['siswaTerlibat']) }}
+
Siswa Terlibat
+
+
+ +
+
+
+
+
+
+
+
{{ $stats['jenisTerbanyak']['jenis'] ?? 'Belum Ada' }}
+
Jenis Terbanyak
+
+
+ +
+
+
- {{-- Preview ringkas --}} -
-
-
{{ $totalKasus }}
-
Total Kasus Pelanggaran
+ {{-- ===== Rekap: per Jenis & per Kelas ===== --}} +
+ {{-- Per Jenis --}} +
+
+

+ + Rekap per Jenis Pelanggaran +

+ {{ $stats['perJenis']->count() }} jenis +
+ @forelse ($stats['perJenis']->take(8) as $item) + @php $max = max($stats['perJenis']->max('jumlah'), 1); @endphp +
+
+ {{ $item['jenis'] }} + + {{ $item['jumlah'] }}× · {{ $item['poin'] }} poin + +
+
+
+
+
+ @empty +
+
+ +
+

Belum ada data pada periode ini

+
+ @endforelse
-
-
{{ $totalPoin }}
-
Total Poin
-
-
-
{{ $siswaTerlibat }}
-
Siswa Terlibat
+ + {{-- Per Kelas --}} +
+
+

+ + Rekap per Kelas +

+ {{ $stats['perKelas']->count() }} kelas +
+
+ + + + + + + + + + @forelse ($stats['perKelas']->take(10) as $i => $item) + + + + + + @empty + + + + @endforelse + +
KelasKasusPoin
+ + + {{ strtoupper(substr($item['kelas'], 0, 2)) }} + + {{ $item['kelas'] }} + + + {{ $item['jumlah'] }} + {{ $item['poin'] }}
+
+ +
+

Belum ada data pada periode ini

+
+
+ + {{-- ===== Rincian Terbaru ===== --}} +
+
+

+ + Rincian Pelanggaran Terbaru +

+ + Lihat Semua + +
+
+ + + + + + + + + + + + + @forelse ($recent as $v) + + + + + + + + + @empty + + + + @endforelse + +
TanggalNISNSiswaKelasJenisPoin
{{ $v->violation_date?->format('d/m/Y') }}{{ $v->student?->nisn }}{{ $v->student?->full_name }} + {{ $v->student?->class?->name }} + {{ $v->violationType?->name }} + {{ $v->points }} +
+
+ +
+

Belum ada pelanggaran pada periode ini

+

Ubah periode atau filter kelas untuk melihat data

+
+
+
+
+ + @endsection