diff --git a/app/Http/Controllers/Admin/ViolationExportController.php b/app/Http/Controllers/Admin/ViolationExportController.php new file mode 100644 index 0000000..6672b2f --- /dev/null +++ b/app/Http/Controllers/Admin/ViolationExportController.php @@ -0,0 +1,157 @@ +filled('date_from')) { + $query->whereDate('violation_date', '>=', $request->date_from); + } + if ($request->filled('date_to')) { + $query->whereDate('violation_date', '<=', $request->date_to); + } + + $violations = $query->orderBy('violation_date', 'desc')->get(); + + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setTitle('Data Pelanggaran'); + + // Headers + $headers = [ + 'A1' => 'No', + 'B1' => 'Tanggal', + 'C1' => 'Waktu', + 'D1' => 'NISN', + 'E1' => 'Nama Siswa', + 'F1' => 'Kelas', + 'G1' => 'Jurusan', + 'H1' => 'Jenis Pelanggaran', + 'I1' => 'Kategori', + 'J1' => 'Poin', + 'K1' => 'Sanksi', + 'L1' => 'Lokasi', + 'M1' => 'Deskripsi', + 'N1' => 'Dicatat Oleh', + 'O1' => 'Status Verifikasi', + 'P1' => 'Status Penanganan', + 'Q1' => 'Penanganan', + 'R1' => 'Yang Menangani', + 'S1' => 'Tanggal Dibuat', + ]; + + // Style header + $headerStyle = [ + 'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF'], 'size' => 11], + 'fill' => ['fillType' => Fill::FILL_SOLID, 'startColor' => ['rgb' => '2563EB']], + 'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER, 'vertical' => Alignment::VERTICAL_CENTER], + ]; + + foreach ($headers as $cell => $value) { + $sheet->setCellValue($cell, $value); + } + $sheet->getStyle('A1:S1')->applyFromArray($headerStyle); + + // Column widths + $sheet->getColumnDimension('A')->setWidth(5); + $sheet->getColumnDimension('B')->setWidth(14); + $sheet->getColumnDimension('C')->setWidth(8); + $sheet->getColumnDimension('D')->setWidth(16); + $sheet->getColumnDimension('E')->setWidth(30); + $sheet->getColumnDimension('F')->setWidth(12); + $sheet->getColumnDimension('G')->setWidth(18); + $sheet->getColumnDimension('H')->setWidth(30); + $sheet->getColumnDimension('I')->setWidth(14); + $sheet->getColumnDimension('J')->setWidth(7); + $sheet->getColumnDimension('K')->setWidth(20); + $sheet->getColumnDimension('L')->setWidth(12); + $sheet->getColumnDimension('M')->setWidth(40); + $sheet->getColumnDimension('N')->setWidth(20); + $sheet->getColumnDimension('O')->setWidth(18); + $sheet->getColumnDimension('P')->setWidth(18); + $sheet->getColumnDimension('Q')->setWidth(30); + $sheet->getColumnDimension('R')->setWidth(30); + $sheet->getColumnDimension('S')->setWidth(16); + + // Data + $row = 2; + foreach ($violations as $i => $v) { + $handlingTypes = $v->handlings->pluck('handling_type')->implode(', '); + $participants = $v->handlings->flatMap(function ($h) { + return $h->participants->map(fn($p) => ($p->user->name ?? '') . ($p->role ? " ({$p->role})" : '')); + })->implode(', '); + + $statusVerifikasi = $v->is_verified ? 'Terverifikasi' : 'Belum'; + $statusLabels = ['unhandled' => 'Belum Ditangani', 'in_progress' => 'Dalam Proses', 'resolved' => 'Selesai']; + $statusPenanganan = $statusLabels[$v->handling_status] ?? $v->handling_status; + + $sheet->setCellValue("A{$row}", $i + 1); + $sheet->setCellValue("B{$row}", $v->violation_date->format('d/m/Y')); + $sheet->setCellValue("C{$row}", $v->violation_time ? \Carbon\Carbon::parse($v->violation_time)->format('H:i') : ''); + $sheet->setCellValue("D{$row}", $v->student->nisn ?? ''); + $sheet->setCellValue("E{$row}", $v->student->full_name ?? ''); + $sheet->setCellValue("F{$row}", $v->student->class_name ?? ''); + $sheet->setCellValue("G{$row}", $v->student->department_name ?? ''); + $sheet->setCellValue("H{$row}", $v->violationType->name ?? ''); + $sheet->setCellValue("I{$row}", $v->violationType?->category?->name ?? ''); + $sheet->setCellValue("J{$row}", $v->points); + $sheet->setCellValue("K{$row}", $v->sanction ?? ''); + $sheet->setCellValue("L{$row}", $v->location ?? ''); + $sheet->setCellValue("M{$row}", $v->description ?? ''); + $sheet->setCellValue("N{$row}", $v->recorder->name ?? ''); + $sheet->setCellValue("O{$row}", $statusVerifikasi); + $sheet->setCellValue("P{$row}", $statusPenanganan); + $sheet->setCellValue("Q{$row}", $handlingTypes); + $sheet->setCellValue("R{$row}", $participants); + $sheet->setCellValue("S{$row}", $v->created_at->format('d/m/Y H:i')); + + // Alternate row color + if ($i % 2 === 0) { + $sheet->getStyle("A{$row}:S{$row}") + ->getFill() + ->setFillType(Fill::FILL_SOLID) + ->getStartColor() + ->setRGB('F8FAFC'); + } + + $sheet->getStyle("A{$row}:S{$row}")->getAlignment()->setVertical(Alignment::VERTICAL_TOP); + $row++; + } + + // Auto filter + $sheet->setAutoFilter("A1:S" . ($row - 1)); + + // Borders + $styleArray = [ + 'borders' => [ + 'allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => 'E2E8F0']], + ], + ]; + $sheet->getStyle("A1:S" . ($row - 1))->applyFromArray($styleArray); + + $writer = new Xlsx($spreadsheet); + $filename = 'data-pelanggaran-' . date('Y-m-d') . '.xlsx'; + + return response()->streamDownload(function () use ($writer) { + $writer->save('php://output'); + }, $filename, [ + 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ]); + } +} diff --git a/app/Http/Controllers/StudentReportController.php b/app/Http/Controllers/StudentReportController.php index 8653fcc..f44c104 100644 --- a/app/Http/Controllers/StudentReportController.php +++ b/app/Http/Controllers/StudentReportController.php @@ -58,7 +58,7 @@ class StudentReportController extends Controller public function show(Student $student): View { $student->load(['violations' => function ($q) { - $q->with(['violationType.category', 'recorder', 'evidences']) + $q->with(['violationType.category', 'recorder', 'evidences', 'handlings.participants.user']) ->latest() ->take(50); }, 'spLetters' => function ($q) { diff --git a/app/Http/Controllers/ViolationController.php b/app/Http/Controllers/ViolationController.php index f1244e5..37b000f 100644 --- a/app/Http/Controllers/ViolationController.php +++ b/app/Http/Controllers/ViolationController.php @@ -89,7 +89,8 @@ class ViolationController extends Controller { $validated = $request->validate([ 'student_id' => ['required', 'exists:students,id'], - 'violation_type_id' => ['required', 'exists:violation_types,id'], + 'violation_type_ids' => ['required', 'array', 'min:1', 'max:10'], + 'violation_type_ids.*' => ['required', 'exists:violation_types,id'], 'description' => ['nullable', 'string', 'max:1000'], 'location' => ['nullable', 'string', 'max:255'], 'violation_date' => ['required', 'date'], @@ -97,19 +98,27 @@ class ViolationController extends Controller 'notes' => ['nullable', 'string', 'max:1000'], 'evidences' => ['nullable', 'array', 'max:5'], 'evidences.*' => ['image', 'mimes:jpeg,png,jpg,gif,webp', 'max:5120'], - 'sanction' => ['nullable', 'string', 'max:500'], ]); - $violationType = ViolationType::findOrFail($validated['violation_type_id']); - $validated['points'] = $violationType->points; - $validated['sanction'] = $validated['sanction'] ?? $violationType->default_sanction; + $evidences = $request->file('evidences') ?? []; + $created = 0; + + foreach ($validated['violation_type_ids'] as $typeId) { + $type = ViolationType::findOrFail($typeId); + + $data = array_merge($validated, [ + 'violation_type_id' => $typeId, + 'points' => $type->points, + 'sanction' => $type->default_sanction, + 'evidences' => $evidences, + ]); + + $this->violationService->recordViolation($data, $request->user()->id); + $created++; + } - //$this->violationService->recordViolation($validated, $request->user()->id); - $data = $validated; - $data['evidences'] = $request->file('evidences') ?? []; - $this->violationService->recordViolation($data, $request->user()->id); return redirect()->route('violations.index') - ->with('success', 'Pelanggaran berhasil dicatat.'); + ->with('success', "{$created} pelanggaran berhasil dicatat."); } public function show(Violation $violation): View diff --git a/app/Models/Violation.php b/app/Models/Violation.php index cacb86a..88a1fa1 100644 --- a/app/Models/Violation.php +++ b/app/Models/Violation.php @@ -26,6 +26,7 @@ class Violation extends Model 'violation_time' => 'datetime:H:i', 'is_verified' => 'boolean', 'verified_at' => 'datetime', + 'handled_at' => 'datetime', 'points' => 'integer', ]; } diff --git a/resources/views/students/show.blade.php b/resources/views/students/show.blade.php index 06f04a4..b708998 100644 --- a/resources/views/students/show.blade.php +++ b/resources/views/students/show.blade.php @@ -205,73 +205,128 @@ @forelse($student->violations as $v) + @php + $handleStatusLabels = [ + 'unhandled' => ['label' => 'Belum Ditangani', 'class' => 'bg-red-50 text-red-600 border-red-200'], + 'in_progress' => ['label' => 'Dalam Proses', 'class' => 'bg-yellow-50 text-yellow-600 border-yellow-200'], + 'resolved' => ['label' => 'Selesai', 'class' => 'bg-green-50 text-green-600 border-green-200'], + ]; + $hs = $handleStatusLabels[$v->handling_status] ?? ['label' => $v->handling_status, 'class' => 'bg-gray-50 text-gray-600']; + @endphp
+ {{-- Timeline dot --}}
-
-
-
- {{ $v->violationType->name ?? '—' }} - @if($v->is_verified) - - - Terverifikasi +
+ {{-- Top row: violation name + badges --}} +
+
+
+ {{ $v->violationType->name ?? '—' }} + {{-- Verification badge --}} + @if($v->is_verified) + + + Terverifikasi + + @endif + {{-- Handling badge --}} + + + {{ $hs['label'] }} +
+ @if($v->description) +

{{ $v->description }}

@endif
- @if($v->description) -

{{ $v->description }}

- @endif -
- - - {{ $v->violation_date->format('d M Y') }} - @if($v->violation_time) {{ \Carbon\Carbon::parse($v->violation_time)->format('H:i') }} @endif - - @if($v->location) - - - {{ $v->location }} - - @endif - - - {{ $v->recorder->name ?? '—' }} - -
- @if($v->evidences && $v->evidences->count() > 0) -
- @foreach($v->evidences->take(3) as $ev) - - Bukti - - @endforeach - @if($v->evidences->count() > 3) -
- +{{ $v->evidences->count() - 3 }} -
- @endif -
- @endif + {{-- Poin --}} +
+ + +{{ $v->points }} + + @if($v->sanction) + {{ $v->sanction }} + @endif +
-
- - +{{ $v->points }} + {{-- Metadata row --}} +
+ + + {{ $v->violation_date->format('d M Y') }} + @if($v->violation_time) {{ \Carbon\Carbon::parse($v->violation_time)->format('H:i') }} @endif - @if($v->sanction) - {{ $v->sanction }} + @if($v->location) + + + {{ $v->location }} + @endif + + + {{ $v->recorder->name ?? '—' }} + +
+ + {{-- Evidence thumbs --}} + @if($v->evidences && $v->evidences->count() > 0) +
+ @foreach($v->evidences->take(3) as $ev) + + Bukti + + @endforeach + @if($v->evidences->count() > 3) +
+ +{{ $v->evidences->count() - 3 }} +
+ @endif +
+ @endif + + {{-- Handlings --}} + @if($v->handlings && $v->handlings->count() > 0) +
+ @foreach($v->handlings as $h) +
+
+ +
+ {{ $h->handling_type }} + {{ $h->handling_date->format('d/m') }} + @if($h->participants->count() > 0) + + @foreach($h->participants as $p) + + + {{ strtoupper(substr($p->user->name ?? '?', 0, 1)) }} + + {{ $p->user->name }} + @if($p->role) + {{ $p->role }} + @endif + + @endforeach + @endif +
+ @endforeach +
+ @endif + + {{-- Detail link --}} +
diff --git a/resources/views/violations/create.blade.php b/resources/views/violations/create.blade.php index 8a978af..8623868 100644 --- a/resources/views/violations/create.blade.php +++ b/resources/views/violations/create.blade.php @@ -4,206 +4,334 @@ @section('content')
-
-

Input Pelanggaran

-

Catat pelanggaran siswa baru

+ {{-- Header --}} +
+
+ +

Input Pelanggaran

+

Catat pelanggaran siswa baru

+
-
+ @csrf -
- {{-- Search Siswa --}} -
- -
- - - -
- -
- -
-
-
- -
-
-

-

-
-
- -
-
- @error('student_id')

{{ $message }}

@enderror -
- - {{-- Jenis Pelanggaran + Poin & Sanksi in ONE scope --}} -
- -
- - - - -
- -
- -
-

Tidak ditemukan untuk ""

-
- -
-
- -
-

-

-
-
-
- - -
-
-
- @error('violation_type_id')

{{ $message }}

@enderror - - {{-- Poin & Sanksi -- same scope as typeSearch --}} -
-
- - -
-
- - -
-
-
- - {{-- Tanggal & Waktu --}} -
-
- - + {{-- Gradient header --}} +
+
+
+
- - +

Form Pelanggaran Baru

+

Isi data pelanggaran dengan lengkap

- - {{-- Lokasi --}} -
- - -
- - {{-- Deskripsi --}} -
- - -
- - {{-- Foto Bukti --}} -
- - - -
- - -
- -
- - -
- @error('evidences.*')

{{ $message }}

@enderror -
- {{-- Submit --}} -
- Batal - +
+
+ @error('student_id')

{{ $message }}

@enderror +
+
+ + {{-- ===== BAGIAN 2: PELANGGARAN ===== --}} +
+
+
+ +
+ Detail Pelanggaran +
+ +
+
+ +

Cari dan pilih satu atau lebih pelanggaran yang dilakukan

+ + {{-- Search with dropdown --}} +
+
+ + +
+ + {{-- Search results --}} +
+ +
+ + {{-- No results --}} +
+

Tidak ditemukan untuk ""

+
+
+ @error('violation_type_ids')

{{ $message }}

@enderror + @error('violation_type_ids.*')

{{ $message }}

@enderror +
+ + {{-- Selected types list --}} + + + {{-- Empty state --}} + +
+
+ + {{-- ===== BAGIAN 3: WAKTU & LOKASI ===== --}} +
+
+
+ +
+ Waktu & Lokasi +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + {{-- ===== BAGIAN 4: CATATAN ===== --}} +
+
+
+ +
+ Catatan +
+ +
+ + +
+
+ + {{-- ===== BAGIAN 5: BUKTI FOTO ===== --}} +
+
+
+ +
+ Bukti Foto + (maks. 5 foto) +
+ + + +
+ + + {{-- Upload placeholder --}} + +
+ +
+ + +
+ @error('evidences.*')

{{ $message }}

@enderror +
+
+ + {{-- Footer --}} +
+ + + Batal + +
@@ -211,40 +339,47 @@ @push('scripts')