feat: penanganan pelanggaran (handling system)

- Migration: handling_status, handled_at, handled_by ke violations
- Migration: tabel violation_handlings, handling_participants
- Model: ViolationHandling, HandlingParticipant + relasi
- Routes: storeHandling, resolveHandling, destroyHandling, calendar data
- ViolationController: CRUD penanganan + filter status handling
- DashboardController: data handling stats + calendar endpoint
- Dashboard view: kalender pelanggaran interaktif (Alpine.js)
- Violations index: filter status penanganan + kolom status
- Violations show: stat card penanganan + timeline + modal tambah
- Menambahkan handling_status filter di index
This commit is contained in:
Bro
2026-07-22 22:19:19 +07:00
parent f86aa84d12
commit 8529a49174
24 changed files with 2152 additions and 842 deletions
@@ -10,6 +10,7 @@ use Illuminate\Http\Request;
use Illuminate\Support\Str;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Style\Border;
class ImportController extends Controller
{
@@ -67,6 +68,68 @@ class ImportController extends Controller
]);
}
public function exportViolationTypes()
{
$types = ViolationType::with('category')->orderBy('category_id')->orderBy('name')->get();
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setTitle('Jenis Pelanggaran');
// Header styling
$headerStyle = [
'font' => ['bold' => true, 'color' => ['rgb' => 'FFFFFF']],
'fill' => [
'fillType' => \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID,
'startColor' => ['rgb' => '2563EB'],
],
'borders' => [
'allBorders' => ['borderStyle' => Border::BORDER_THIN, 'color' => ['rgb' => 'BFDBFE']],
],
];
// Headers
$headers = ['No', 'Kategori', 'Nama Pelanggaran', 'Poin', 'Sanksi Default', 'Deskripsi', 'Status'];
foreach (range('A', 'G') as $i => $col) {
$sheet->setCellValue($col . '1', $headers[$i]);
}
$sheet->getStyle('A1:G1')->applyFromArray($headerStyle);
// Data
$row = 2;
foreach ($types as $i => $type) {
$sheet->setCellValue('A' . $row, $i + 1);
$sheet->setCellValue('B' . $row, $type->category?->name ?? '-');
$sheet->setCellValue('C' . $row, $type->name);
$sheet->setCellValue('D' . $row, $type->points);
$sheet->setCellValue('E' . $row, $type->default_sanction ?? '');
$sheet->setCellValue('F' . $row, $type->description ?? '');
$sheet->setCellValue('G' . $row, $type->is_active ? 'Aktif' : 'Nonaktif');
// Zebra striping
if ($i % 2 === 0) {
$sheet->getStyle('A' . $row . ':G' . $row)
->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
->getStartColor()->setARGB('FFF0F5FF');
}
$row++;
}
// Autofit
foreach (range('A', 'G') as $col) {
$sheet->getColumnDimension($col)->setAutoSize(true);
}
$writer = new Xlsx($spreadsheet);
return response()->streamDownload(function () use ($writer) {
$writer->save('php://output');
}, 'jenis-pelanggaran.xlsx', [
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
]);
}
public function importViolationTypes(Request $request): RedirectResponse
{
$request->validate([
+26 -1
View File
@@ -6,6 +6,7 @@ use App\Models\AppNotification;
use App\Models\Student;
use App\Models\Violation;
use App\Models\SpLetter;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
@@ -20,6 +21,9 @@ class DashboardController extends Controller
'total_violations' => Violation::count(),
'total_students' => Student::where('is_active', true)->count(),
'active_sp' => SpLetter::where('status', 'draft')->count(),
'unhandled_violations' => Violation::where('handling_status', 'unhandled')->count(),
'in_progress_violations' => Violation::where('handling_status', 'in_progress')->count(),
'resolved_violations' => Violation::where('handling_status', 'resolved')->count(),
];
$recentViolations = Violation::with(['student', 'violationType.category', 'recorder'])
@@ -49,12 +53,33 @@ class DashboardController extends Controller
$spThresholds = \App\Models\SpThreshold::where('is_active', true)->get();
// Data untuk calendar: pelanggaran per tanggal di bulan ini
$calendarData = Violation::selectRaw('DATE(violation_date) as date, COUNT(*) as count')
->whereMonth('violation_date', now()->month)
->whereYear('violation_date', now()->year)
->groupBy('date')
->pluck('count', 'date');
return view('dashboard.index', compact(
'stats', 'recentViolations', 'topStudents',
'unreadNotifications', 'notificationCount', 'spThresholds'
'unreadNotifications', 'notificationCount', 'spThresholds', 'calendarData'
));
}
public function getCalendarData(Request $request): JsonResponse
{
$year = $request->input('year', now()->year);
$month = $request->input('month', now()->month);
$data = Violation::selectRaw('DATE(violation_date) as date, COUNT(*) as count')
->whereYear('violation_date', $year)
->whereMonth('violation_date', $month)
->groupBy('date')
->pluck('count', 'date');
return response()->json($data);
}
public function markNotificationRead(Request $request, $id)
{
$notif = AppNotification::where('user_id', $request->user()->id)->findOrFail($id);
@@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\Models\Student;
use Illuminate\Http\RedirectResponse;
use App\Models\SpLetter;
use App\Models\SpThreshold;
use App\Models\Violation;
@@ -88,4 +89,28 @@ class StudentReportController extends Controller
'spThresholds', 'currentSpLevel', 'nextSpThreshold', 'activeSpLetters'
));
}
public function update(Request $request, Student $student): RedirectResponse
{
$validated = $request->validate([
'full_name' => ['required', 'string', 'max:255'],
'nisn' => ['nullable', 'string', 'max:50'],
'student_number' => ['nullable', 'string', 'max:50'],
'gender' => ['nullable', 'in:L,P'],
'place_of_birth' => ['nullable', 'string', 'max:255'],
'date_of_birth' => ['nullable', 'date'],
'address' => ['nullable', 'string', 'max:500'],
'phone_number' => ['nullable', 'string', 'max:30'],
'email' => ['nullable', 'string', 'email', 'max:255'],
'class_name' => ['nullable', 'string', 'max:100'],
'class_level' => ['nullable', 'string', 'max:20'],
'department_code' => ['nullable', 'string', 'max:20'],
'department_name' => ['nullable', 'string', 'max:100'],
]);
$student->update($validated);
return redirect()->route('students.show', $student->id)
->with('success', 'Data siswa berhasil diperbarui.');
}
}
+86 -2
View File
@@ -4,8 +4,10 @@ namespace App\Http\Controllers;
use App\Models\Student;
use App\Models\Violation;
use App\Models\ViolationHandling;
use App\Models\ViolationType;
use App\Models\AppNotification;
use App\Models\HandlingParticipant;
use App\Services\ViolationService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@@ -52,6 +54,10 @@ class ViolationController extends Controller
$query->whereDate('violation_date', '<=', $request->date_to);
}
if ($request->filled('handling_status')) {
$query->where('handling_status', $request->handling_status);
}
$violations = $query->latest()->paginate(20);
$categories = \App\Models\ViolationCategory::where('is_active', true)->get();
@@ -108,9 +114,87 @@ class ViolationController extends Controller
public function show(Violation $violation): View
{
$violation->load(['student', 'violationType.category', 'recorder', 'evidences']);
$violation->load([
'student', 'violationType.category', 'recorder', 'evidences',
'handlings.participants.user', 'handlings.creator',
]);
return view('violations.show', compact('violation'));
$users = \App\Models\User::where('is_active', true)->orderBy('name')->get();
return view('violations.show', compact('violation', 'users'));
}
public function storeHandling(Request $request, Violation $violation): RedirectResponse
{
$validated = $request->validate([
'handling_type' => ['required', 'string', 'max:50'],
'handling_date' => ['required', 'date'],
'description' => ['nullable', 'string', 'max:2000'],
'location' => ['nullable', 'string', 'max:255'],
'evidence' => ['nullable', 'file', 'mimes:jpeg,png,jpg,gif,webp,pdf,doc,docx', 'max:10240'],
'participants' => ['nullable', 'array', 'max:20'],
'participants.*.user_id' => ['required_with:participants', 'exists:users,id'],
'participants.*.role' => ['nullable', 'string', 'max:50'],
]);
$evidencePath = null;
if ($request->hasFile('evidence')) {
$evidencePath = $request->file('evidence')->store('handling-evidence', 'public');
}
$handling = ViolationHandling::create([
'violation_id' => $violation->id,
'handling_type' => $validated['handling_type'],
'handling_date' => $validated['handling_date'],
'description' => $validated['description'] ?? null,
'location' => $validated['location'] ?? null,
'evidence' => $evidencePath,
'created_by' => $request->user()->id,
]);
// Add participants
if (!empty($validated['participants'])) {
foreach ($validated['participants'] as $p) {
HandlingParticipant::create([
'handling_id' => $handling->id,
'user_id' => $p['user_id'],
'role' => $p['role'] ?? null,
]);
}
}
// Update violation status
if ($violation->isUnhandled()) {
$violation->update(['handling_status' => 'in_progress']);
}
return redirect()->route('violations.show', $violation->id)
->with('success', 'Penanganan berhasil ditambahkan.');
}
public function resolveHandling(Request $request, Violation $violation): RedirectResponse
{
$violation->update([
'handling_status' => 'resolved',
'handled_at' => now(),
'handled_by' => $request->user()->id,
]);
return redirect()->route('violations.show', $violation->id)
->with('success', 'Pelanggaran ditandai selesai ditangani.');
}
public function destroyHandling(Violation $violation, ViolationHandling $handling): RedirectResponse
{
$handling->delete();
// If no more handlings, revert to unhandled
if ($violation->handlings()->count() === 0) {
$violation->update(['handling_status' => 'unhandled']);
}
return redirect()->route('violations.show', $violation->id)
->with('success', 'Catatan penanganan berhasil dihapus.');
}
public function verify(Request $request, Violation $violation): RedirectResponse
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class HandlingParticipant extends Model
{
protected $fillable = ['handling_id', 'user_id', 'role'];
public function handling(): BelongsTo
{
return $this->belongsTo(ViolationHandling::class, 'handling_id');
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
+10
View File
@@ -38,4 +38,14 @@ class User extends Authenticatable
{
return $this->hasMany(AppNotification::class, 'user_id');
}
public function handlingParticipants()
{
return $this->hasMany(HandlingParticipant::class);
}
public function createdHandlings()
{
return $this->hasMany(ViolationHandling::class, 'created_by');
}
}
+26 -1
View File
@@ -16,7 +16,7 @@ class Violation extends Model
'description', 'points', 'sanction', 'location',
'violation_date', 'violation_time',
'is_verified', 'verified_by', 'verified_at',
'notes',
'notes', 'handling_status', 'handled_at', 'handled_by',
];
protected function casts(): array
@@ -54,4 +54,29 @@ class Violation extends Model
{
return $this->hasMany(ViolationEvidence::class);
}
public function handlings(): HasMany
{
return $this->hasMany(ViolationHandling::class);
}
public function handler(): BelongsTo
{
return $this->belongsTo(User::class, 'handled_by');
}
public function isUnhandled(): bool
{
return $this->handling_status === 'unhandled';
}
public function isInProgress(): bool
{
return $this->handling_status === 'in_progress';
}
public function isResolved(): bool
{
return $this->handling_status === 'resolved';
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class ViolationHandling extends Model
{
protected $fillable = [
'violation_id', 'handling_type', 'handling_date',
'description', 'location', 'evidence', 'created_by',
];
protected function casts(): array
{
return [
'handling_date' => 'date',
];
}
public function violation(): BelongsTo
{
return $this->belongsTo(Violation::class);
}
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}
public function participants(): HasMany
{
return $this->hasMany(HandlingParticipant::class, 'handling_id');
}
}
@@ -0,0 +1,25 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('violations', function (Blueprint $table) {
$table->string('handling_status', 20)->default('unhandled')->after('recorded_by')->comment('unhandled|in_progress|resolved');
$table->timestamp('handled_at')->nullable()->after('handling_status');
$table->foreignId('handled_by')->nullable()->constrained('users')->nullOnDelete()->after('handled_at');
});
}
public function down(): void
{
Schema::table('violations', function (Blueprint $table) {
$table->dropConstrainedForeignId('handled_by');
$table->dropColumn(['handling_status', 'handled_at']);
});
}
};
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('violation_handlings', function (Blueprint $table) {
$table->id();
$table->foreignId('violation_id')->constrained()->cascadeOnDelete();
$table->string('handling_type', 50);
$table->date('handling_date');
$table->text('description')->nullable();
$table->string('location', 255)->nullable();
$table->string('evidence', 255)->nullable();
$table->unsignedBigInteger('created_by')->nullable()->index();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('violation_handlings');
}
};
@@ -0,0 +1,25 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('handling_participants', function (Blueprint $table) {
$table->id();
$table->foreignId('handling_id')->constrained('violation_handlings')->cascadeOnDelete();
$table->unsignedBigInteger('user_id');
$table->string('role', 50)->nullable()->comment('peran dalam penanganan: Wakil Ketua Tim, Anggota Tim, Wali Kelas, Guru BK, Saksi, dll');
$table->timestamps();
$table->unique(['handling_id', 'user_id']);
});
}
public function down(): void
{
Schema::dropIfExists('handling_participants');
}
};
+285 -85
View File
@@ -4,96 +4,146 @@
@section('content')
<div>
{{-- Header --}}
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900">Dashboard</h1>
<p class="text-sm text-gray-500">Ringkasan pelanggaran siswa</p>
<h1 class="text-2xl font-bold text-gray-900 tracking-tight">Dashboard</h1>
<p class="text-sm text-gray-500 mt-1">Ringkasan pelanggaran siswa</p>
</div>
{{-- Stats Cards --}}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-gray-500">Pelanggaran Hari Ini</p>
<p class="text-3xl font-bold text-blue-600 mt-1">{{ $stats['today_violations'] }}</p>
</div>
<div class="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center">
<i class="fa-solid fa-triangle-exclamation text-blue-600 text-xl"></i>
</div>
{{-- Gradient Stats Cards --}}
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
<div class="relative overflow-hidden rounded-2xl bg-gradient-to-br from-orange-500 to-red-600 p-5 shadow-sm">
<div class="absolute right-0 top-0 w-20 h-20 opacity-10">
<i class="fa-solid fa-triangle-exclamation text-white text-6xl"></i>
</div>
<div class="relative z-10">
<p class="text-xs font-semibold text-white/70 uppercase tracking-wider">Pelanggaran Hari Ini</p>
<p class="text-3xl font-bold text-white mt-1">{{ $stats['today_violations'] }}</p>
<p class="text-[10px] text-white/50 mt-0.5">data terbaru</p>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-gray-500">Total Pelanggaran</p>
<p class="text-3xl font-bold text-orange-600 mt-1">{{ $stats['total_violations'] }}</p>
</div>
<div class="w-12 h-12 bg-orange-100 rounded-full flex items-center justify-center">
<i class="fa-solid fa-list-check text-orange-600 text-xl"></i>
</div>
<div class="relative overflow-hidden rounded-2xl bg-gradient-to-br from-blue-500 to-blue-700 p-5 shadow-sm">
<div class="absolute right-0 top-0 w-20 h-20 opacity-10">
<i class="fa-solid fa-list-check text-white text-6xl"></i>
</div>
<div class="relative z-10">
<p class="text-xs font-semibold text-white/70 uppercase tracking-wider">Total Pelanggaran</p>
<p class="text-3xl font-bold text-white mt-1">{{ $stats['total_violations'] }}</p>
<p class="text-[10px] text-white/50 mt-0.5">semua waktu</p>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-gray-500">Siswa Aktif</p>
<p class="text-3xl font-bold text-blue-600 mt-1">{{ $stats['total_students'] }}</p>
</div>
<div class="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center">
<i class="fa-solid fa-users text-blue-600 text-xl"></i>
</div>
<div class="relative overflow-hidden rounded-2xl bg-gradient-to-br from-emerald-500 to-emerald-700 p-5 shadow-sm">
<div class="absolute right-0 top-0 w-20 h-20 opacity-10">
<i class="fa-solid fa-users text-white text-6xl"></i>
</div>
<div class="relative z-10">
<p class="text-xs font-semibold text-white/70 uppercase tracking-wider">Siswa Aktif</p>
<p class="text-3xl font-bold text-white mt-1">{{ $stats['total_students'] }}</p>
<p class="text-[10px] text-white/50 mt-0.5">terdata</p>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-gray-500">SP Aktif (Draft)</p>
<p class="text-3xl font-bold text-yellow-600 mt-1">{{ $stats['active_sp'] }}</p>
</div>
<div class="w-12 h-12 bg-yellow-100 rounded-full flex items-center justify-center">
<i class="fa-solid fa-file-lines text-yellow-600 text-xl"></i>
</div>
<div class="relative overflow-hidden rounded-2xl bg-gradient-to-br from-violet-500 to-violet-700 p-5 shadow-sm">
<div class="absolute right-0 top-0 w-20 h-20 opacity-10">
<i class="fa-solid fa-file-lines text-white text-6xl"></i>
</div>
<div class="relative z-10">
<p class="text-xs font-semibold text-white/70 uppercase tracking-wider">SP Draft</p>
<p class="text-3xl font-bold text-white mt-1">{{ $stats['active_sp'] }}</p>
<p class="text-[10px] text-white/50 mt-0.5">menunggu tindak lanjut</p>
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
{{-- Recent Violations --}}
<div class="lg:col-span-2 bg-white rounded-xl shadow-sm border border-gray-200">
<div class="px-5 py-4 border-b border-gray-100">
<h2 class="text-lg font-semibold text-gray-900">Pelanggaran Terbaru</h2>
{{-- Calendar --}}
<div class="lg:col-span-2 bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden"
x-data="calendarApp()" x-init="init()">
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center shadow-sm">
<i class="fa-solid fa-calendar text-white text-sm"></i>
</div>
<div>
<h2 class="text-sm font-semibold text-gray-900">Kalender Pelanggaran</h2>
<p class="text-base font-bold text-gray-700" x-text="monthLabel + ' ' + year"></p>
</div>
</div>
<div class="flex items-center gap-1">
<button @click="prevMonth()" class="p-2 rounded-lg hover:bg-gray-100 text-gray-400 hover:text-gray-600 transition">
<i class="fa-solid fa-chevron-left text-xs"></i>
</button>
<button @click="nextMonth()" class="p-2 rounded-lg hover:bg-gray-100 text-gray-400 hover:text-gray-600 transition">
<i class="fa-solid fa-chevron-right text-xs"></i>
</button>
</div>
</div>
<div class="divide-y divide-gray-100">
@forelse($recentViolations as $v)
<a href="{{ route('violations.show', $v->id) }}" class="flex items-center justify-between px-5 py-3 hover:bg-gray-50 transition">
<div class="flex items-center space-x-3">
<span class="w-2.5 h-2.5 rounded-full flex-shrink-0" style="background-color: {{ $v->violationType?->category?->color ?? '#6b7280' }}"></span>
<div>
<p class="text-sm font-medium text-gray-900">{{ $v->student->full_name ?? '-' }}</p>
<p class="text-xs text-gray-500">{{ $v->violationType->name ?? '-' }} <span class="text-red-500 font-medium">+{{ $v->points }} poin</span></p>
<div class="p-5">
{{-- Day headers --}}
<div class="grid grid-cols-7 mb-2">
<template x-for="day in ['Min', 'Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab']">
<div class="text-center text-xs font-semibold text-gray-400 uppercase tracking-wider py-2" x-text="day"></div>
</template>
</div>
{{-- Calendar grid --}}
<div class="grid grid-cols-7 gap-1">
<template x-for="(day, idx) in days" :key="idx">
<div
class="relative min-h-[70px] sm:min-h-[80px] rounded-xl border transition-all duration-150 p-1.5"
:class="day.isToday
? 'border-blue-300 bg-blue-50/50 ring-1 ring-blue-200'
: day.isCurrentMonth
? 'border-gray-100 hover:border-gray-200 hover:bg-gray-50'
: 'border-gray-50 bg-gray-50/30 text-gray-300'">
{{-- Date number --}}
<div class="text-xs font-semibold"
:class="day.isToday ? 'text-blue-600' : (day.isCurrentMonth ? 'text-gray-700' : 'text-gray-300')"
x-text="day.day">
</div>
{{-- Violation badge --}}
<template x-if="day.count > 0 && day.isCurrentMonth">
<a :href="'{{ route('violations.index') }}?date_from=' + day.dateStr + '&date_to=' + day.dateStr"
class="absolute bottom-1.5 right-1.5 inline-flex items-center justify-center min-w-[26px] h-[26px] text-xs font-bold text-white rounded-full shadow-sm"
:class="day.count >= 3 ? 'bg-red-500' : (day.count >= 2 ? 'bg-orange-400' : 'bg-blue-400')"
x-text="day.count">
</a>
</template>
</div>
<div class="text-right text-xs text-gray-400">
<p>{{ $v->violation_date->format('d/m/Y') }}</p>
<p>{{ $v->recorder->name ?? '-' }}</p>
</div>
</a>
@empty
<div class="px-5 py-8 text-center text-sm text-gray-500">Belum ada pelanggaran tercatat</div>
@endforelse
</template>
</div>
</div>
<div class="px-5 py-3 border-t border-gray-100">
<a href="{{ route('violations.index') }}" class="text-sm text-blue-600 hover:text-blue-800 font-medium">Lihat semua &rarr;</a>
{{-- Legend --}}
<div class="px-6 py-3 border-t border-gray-100 bg-gray-50/50 flex items-center gap-4 text-[11px] text-gray-500">
<span class="inline-flex items-center gap-1.5">
<span class="w-2.5 h-2.5 rounded bg-blue-400"></span> 1
</span>
<span class="inline-flex items-center gap-1.5">
<span class="w-2.5 h-2.5 rounded bg-orange-400"></span> 2
</span>
<span class="inline-flex items-center gap-1.5">
<span class="w-2.5 h-2.5 rounded bg-red-500"></span> 3+
</span>
<span class="ml-auto text-gray-400">Klik badge untuk lihat detail</span>
</div>
</div>
{{-- Top Students + SP Alerts --}}
{{-- Right Sidebar --}}
<div class="space-y-6">
{{-- Notifikasi Ambang SP --}}
{{-- SP Thresholds --}}
@if($spThresholds->count() > 0)
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
<div class="px-5 py-4 border-b border-gray-100">
<h2 class="text-lg font-semibold text-gray-900">Ambang SP</h2>
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-yellow-500 to-orange-500 flex items-center justify-center shadow-sm">
<i class="fa-solid fa-chart-bar text-white text-sm"></i>
</div>
<div>
<h2 class="text-sm font-semibold text-gray-900">Ambang SP</h2>
<p class="text-xs text-gray-400">Siswa per level</p>
</div>
</div>
</div>
<div class="p-5 space-y-3">
@foreach($spThresholds as $threshold)
@@ -103,42 +153,192 @@
$maxOk = $threshold->max_points ?? 9999;
return $pts >= $threshold->min_points && $pts <= $maxOk;
});
$count = $studentsAtRisk->count();
@endphp
<div class="flex items-center justify-between p-3 rounded-lg border" style="border-color: {{ $threshold->color }}20; background-color: {{ $threshold->color }}08">
<div>
<p class="text-sm font-semibold" style="color: {{ $threshold->color }}">{{ $threshold->name }}</p>
<p class="text-xs text-gray-500">{{ $threshold->min_points }}-{{ $threshold->max_points ?? '~' }} poin</p>
<div class="relative overflow-hidden rounded-xl p-4"
style="background-color: {{ $threshold->color }}08; border: 1px solid {{ $threshold->color }}20;">
<div class="flex items-center justify-between relative z-10">
<div>
<p class="text-sm font-bold" style="color: {{ $threshold->color }}">{{ $threshold->name }}</p>
<p class="text-xs text-gray-400 mt-0.5">{{ $threshold->min_points }}{{ $threshold->max_points ? '–'.$threshold->max_points : '+' }} poin</p>
</div>
<div class="text-center">
<p class="text-2xl font-bold" style="color: {{ $threshold->color }}">{{ $count }}</p>
<p class="text-[10px] text-gray-400">siswa</p>
</div>
</div>
<span class="text-lg font-bold" style="color: {{ $threshold->color }}">{{ $studentsAtRisk->count() }}</span>
@if($count > 0)
<div class="mt-3 h-1.5 bg-gray-100 rounded-full overflow-hidden">
<div class="h-full rounded-full" style="width: {{ min(100, ($count / max(1, $stats['total_students'])) * 100) }}%; background-color: {{ $threshold->color }}"></div>
</div>
@endif
</div>
@endforeach
</div>
</div>
@endif
{{-- Top 5 Siswa Poin Tertinggi --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
<div class="px-5 py-4 border-b border-gray-100">
<h2 class="text-lg font-semibold text-gray-900">Poin Tertinggi</h2>
{{-- Top 5 Students --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-red-500 to-red-600 flex items-center justify-center shadow-sm">
<i class="fa-solid fa-crown text-white text-sm"></i>
</div>
<div>
<h2 class="text-sm font-semibold text-gray-900">Poin Tertinggi</h2>
<p class="text-xs text-gray-400">Top 5 siswa</p>
</div>
</div>
</div>
<div class="divide-y divide-gray-100">
<div class="divide-y divide-gray-50">
@forelse($topStudents as $student)
<div class="flex items-center justify-between px-5 py-3">
<div class="flex items-center space-x-3">
<span class="w-8 h-8 rounded-full bg-blue-100 text-red-700 flex items-center justify-center text-xs font-bold">{{ $loop->iteration }}</span>
<div>
<p class="text-sm font-medium text-gray-900">{{ $student->full_name }}</p>
<p class="text-xs text-gray-500">{{ $student->class_name }}</p>
<div class="flex items-center justify-between px-6 py-3.5 hover:bg-gray-50/50 transition">
<div class="flex items-center gap-3 min-w-0">
<div class="w-8 h-8 rounded-lg flex items-center justify-center text-xs font-bold flex-shrink-0
{{ $loop->first ? 'bg-yellow-100 text-yellow-700' : ($loop->iteration <= 3 ? 'bg-gray-100 text-gray-600' : 'bg-gray-50 text-gray-400') }}">
<i class="fa-solid {{ $loop->first ? 'fa-crown' : ($loop->iteration <= 3 ? 'fa-medal' : 'fa-hashtag') }}"></i>
</div>
<div class="min-w-0">
<p class="text-sm font-semibold text-gray-900 truncate max-w-[140px]">{{ $student->full_name }}</p>
<p class="text-xs text-gray-400">{{ $student->class_name }}</p>
</div>
</div>
<span class="text-sm font-bold text-blue-600">{{ $student->total_points }}</span>
<span class="text-sm font-bold {{ $student->total_points >= 100 ? 'text-red-600' : ($student->total_points >= 50 ? 'text-yellow-600' : 'text-blue-600') }}">
{{ $student->total_points }}
</span>
</div>
@empty
<div class="px-5 py-8 text-center text-sm text-gray-500">Belum ada data</div>
<div class="px-6 py-12 text-center">
<div class="w-12 h-12 rounded-full bg-gray-50 border border-gray-100 flex items-center justify-center mx-auto mb-3">
<i class="fa-solid fa-database text-gray-300 text-lg"></i>
</div>
<p class="text-sm text-gray-500">Belum ada data</p>
</div>
@endforelse
</div>
@if(count($topStudents) > 0)
<div class="px-6 py-3 border-t border-gray-100 bg-gray-50/50">
<a href="{{ route('students.index') }}" class="inline-flex items-center gap-1.5 text-sm font-semibold text-blue-600 hover:text-blue-800 transition">
Lihat semua siswa
<i class="fa-solid fa-arrow-right text-xs"></i>
</a>
</div>
@endif
</div>
</div>
</div>
</div>
@push('scripts')
<script>
function calendarApp() {
return {
year: {{ now()->year }},
month: {{ now()->month }},
days: [],
violations: @json($calendarData),
get monthLabel() {
const months = ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'];
return months[this.month - 1] || '';
},
init() {
this.render();
},
render() {
const firstDay = new Date(this.year, this.month - 1, 1);
const lastDay = new Date(this.year, this.month, 0);
const startPad = firstDay.getDay();
const daysInMonth = lastDay.getDate();
// Prev month days for padding
const prevLastDay = new Date(this.year, this.month - 1, 0);
const prevDaysInMonth = prevLastDay.getDate();
const today = new Date();
const todayStr = today.getFullYear() + '-' + String(today.getMonth() + 1).padStart(2, '0') + '-' + String(today.getDate()).padStart(2, '0');
this.days = [];
// Previous month trailing days
for (let i = startPad - 1; i >= 0; i--) {
const pd = prevDaysInMonth - i;
const m = this.month === 1 ? 12 : this.month - 1;
const y = this.month === 1 ? this.year - 1 : this.year;
const dateStr = y + '-' + String(m).padStart(2, '0') + '-' + String(pd).padStart(2, '0');
this.days.push({
day: pd,
isCurrentMonth: false,
isToday: false,
count: 0,
dateStr: dateStr
});
}
// Current month days
for (let d = 1; d <= daysInMonth; d++) {
const dateStr = this.year + '-' + String(this.month).padStart(2, '0') + '-' + String(d).padStart(2, '0');
const isToday = dateStr === todayStr;
this.days.push({
day: d,
isCurrentMonth: true,
isToday: isToday,
count: this.violations[dateStr] || 0,
dateStr: dateStr
});
}
// Next month leading days (to complete last row)
const remaining = 7 - (this.days.length % 7);
if (remaining < 7) {
for (let d = 1; d <= remaining; d++) {
const m = this.month === 12 ? 1 : this.month + 1;
const y = this.month === 12 ? this.year + 1 : this.year;
const dateStr = y + '-' + String(m).padStart(2, '0') + '-' + String(d).padStart(2, '0');
this.days.push({
day: d,
isCurrentMonth: false,
isToday: false,
count: 0,
dateStr: dateStr
});
}
}
},
prevMonth() {
if (this.month === 1) {
this.month = 12;
this.year--;
} else {
this.month--;
}
this.fetchAndRender();
},
nextMonth() {
if (this.month === 12) {
this.month = 1;
this.year++;
} else {
this.month++;
}
this.fetchAndRender();
},
fetchAndRender() {
fetch('{{ route('calendar.data') }}?year=' + this.year + '&month=' + this.month)
.then(r => r.json())
.then(data => {
this.violations = data;
this.render();
});
}
};
}
</script>
@endpush
@endsection
+21 -7
View File
@@ -29,7 +29,7 @@
<img src="{{ asset('storage/' . $logoPath) }}" alt="Logo" class="w-full h-full object-contain p-0.5">
@else
<div class="w-full h-full bg-gradient-to-br from-blue-500 to-sky-400 flex items-center justify-center">
<svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/></svg>
<i class="fa-solid fa-shield-halved"></i>
</div>
@endif
</div>
@@ -136,9 +136,7 @@
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-blue-500 to-sky-400 flex items-center justify-center text-white text-sm font-bold shadow-sm">
{{ strtoupper(substr(auth()->user()->name, 0, 1)) }}
</div>
<svg class="w-3.5 h-3.5 text-gray-400 transition-transform duration-200" :class="{ 'rotate-180': showUserMenu }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
<i class="fa-solid fa-chevron-down"></i>
</button>
{{-- Dropdown --}}
@@ -168,9 +166,7 @@
@csrf
<button type="submit"
class="w-full flex items-center gap-3 px-4 py-2.5 text-sm text-gray-600 hover:text-blue-600 hover:bg-red-50 transition">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
</svg>
<i class="fa-solid fa-right-from-bracket"></i>
Logout
</button>
</form>
@@ -278,5 +274,23 @@
}));
});
</script>
{{-- Global helpers --}}
<script>
window.confirmSwal = async function (opts = {}) {
const result = await Swal.fire({
title: opts.title || 'Konfirmasi',
text: opts.text || 'Yakin ingin melanjutkan?',
icon: opts.icon || 'warning',
showCancelButton: true,
confirmButtonColor: opts.confirmColor || '#dc2626',
cancelButtonColor: '#6b7280',
confirmButtonText: opts.confirmText || 'Ya, hapus!',
cancelButtonText: 'Batal',
reverseButtons: true,
});
return result.isConfirmed;
};
</script>
</body>
</html>
+3 -9
View File
@@ -49,9 +49,7 @@
<div class="px-6 py-4 border-b border-gray-100">
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-lg bg-blue-50 flex items-center justify-center">
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
</svg>
<i class="fa-solid fa-user"></i>
</div>
<h3 class="text-base font-semibold text-gray-900">Informasi Profil</h3>
</div>
@@ -81,9 +79,7 @@
<div class="px-6 py-4 border-t border-gray-100 border-b border-gray-100">
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-lg bg-blue-50 flex items-center justify-center">
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
</svg>
<i class="fa-solid fa-lock"></i>
</div>
<div>
<h3 class="text-base font-semibold text-gray-900">Ganti Kata Sandi</h3>
@@ -117,9 +113,7 @@
<div class="px-6 py-4 border-t border-gray-100 bg-gray-50/50 rounded-b-2xl flex items-center justify-end">
<button type="submit"
class="px-6 py-2.5 text-sm font-medium text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm flex items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
<i class="fa-solid fa-check"></i>
Simpan Perubahan
</button>
</div>
+8 -18
View File
@@ -12,9 +12,7 @@
</div>
<button @click="openCreate()"
class="inline-flex items-center px-4 py-2.5 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition shadow-sm">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
</svg>
<i class="fa-solid fa-plus"></i>
Tambah Kategori
</button>
</div>
@@ -98,9 +96,7 @@
<input type="hidden" name="is_active" value="1">
<button type="submit"
class="inline-flex items-center px-3 py-1.5 text-xs font-medium text-green-600 bg-green-50 border border-green-200 rounded-lg hover:bg-green-100 transition">
<svg class="w-3.5 h-3.5 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<i class="fa-solid fa-circle-check"></i>
Aktifkan
</button>
</form>
@@ -108,13 +104,11 @@
</div>
@if($typeCount === 0)
<form action="{{ route('settings.categories.destroy', $cat->id) }}" method="POST"
x-data x-on:submit.prevent="if(await window.confirmSwal({text:'Hapus kategori ini?'})) $el.submit()"">
x-data x-on:submit.prevent="if(await window.confirmSwal({text:'Hapus kategori ini?'})) $el.submit()">
@csrf @method('DELETE')
<button type="submit"
class="inline-flex items-center px-3 py-1.5 text-xs font-medium text-blue-600 bg-red-50 border border-blue-200 rounded-lg hover:bg-blue-100 transition">
<svg class="w-3.5 h-3.5 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
<i class="fa-solid fa-trash-can"></i>
Hapus
</button>
</form>
@@ -141,18 +135,16 @@
<div x-show="modalOpen" class="fixed inset-0 z-50 overflow-y-auto" style="display: none;">
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:p-0">
{{-- Overlay --}}
<div x-show="modalOpen" @click="modalOpen = false" class="fixed inset-0 bg-gray-500 bg-opacity-50 transition-opacity"></div>
<div x-show="modalOpen" class="fixed inset-0 bg-gray-500 bg-opacity-50 transition-opacity"></div>
{{-- Panel --}}
<div x-show="modalOpen" @click.away="modalOpen = false"
<div x-show="modalOpen"
class="relative inline-block align-bottom bg-white rounded-2xl shadow-xl border border-gray-200 text-left overflow-hidden transform transition-all sm:align-middle sm:max-w-lg sm:w-full">
{{-- Header --}}
<div class="px-6 py-5 border-b border-gray-100 flex items-center justify-between">
<div class="flex items-center space-x-3">
<div class="w-10 h-10 rounded-lg flex items-center justify-center" :class="isEditing ? 'bg-blue-100' : 'bg-blue-100'">
<svg x-show="!isEditing" class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
</svg>
<i class="fa-solid fa-plus"></i>
<svg x-show="isEditing" class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
@@ -163,9 +155,7 @@
</div>
</div>
<button @click="modalOpen = false" class="text-gray-400 hover:text-gray-600 transition">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
<i class="fa-solid fa-xmark"></i>
</button>
</div>
+4 -12
View File
@@ -34,9 +34,7 @@
<div class="bg-red-50 border border-red-200 rounded-2xl p-5 mb-6">
<div class="flex items-start gap-3">
<div class="w-8 h-8 rounded-lg bg-red-100 flex items-center justify-center flex-shrink-0">
<svg class="w-4 h-4 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<i class="fa-solid fa-triangle-exclamation"></i>
</div>
<div>
<h3 class="text-sm font-semibold text-red-800">⚠️ Peringatan</h3>
@@ -53,9 +51,7 @@
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between">
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-lg bg-red-50 flex items-center justify-center">
<svg class="w-4 h-4 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
<i class="fa-solid fa-trash-can"></i>
</div>
<div>
<h3 class="text-base font-semibold text-gray-900">Pilih Data yang Akan Dikosongkan</h3>
@@ -180,9 +176,7 @@
<div class="px-6 py-4 border-b border-gray-100">
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-lg bg-gray-50 flex items-center justify-center">
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
</svg>
<i class="fa-solid fa-lock"></i>
</div>
<div>
<h3 class="text-base font-semibold text-gray-900">Konfirmasi Password</h3>
@@ -199,9 +193,7 @@
</div>
<button type="submit" id="reset-btn" disabled
class="px-6 py-2.5 text-sm font-semibold text-white bg-gray-300 rounded-xl cursor-not-allowed transition flex items-center justify-center gap-2 whitespace-nowrap">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
<i class="fa-solid fa-trash-can"></i>
Jalankan Reset
</button>
</div>
+4 -12
View File
@@ -22,9 +22,7 @@
@error('base_url') <p class="mt-1 text-sm text-blue-600">{{ $message }}</p> @enderror
@if($baseUrl)
<p class="mt-1.5 text-xs text-green-600 flex items-center">
<svg class="w-3.5 h-3.5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
<i class="fa-solid fa-check"></i>
URL tersimpan: {{ $baseUrl }}
</p>
@endif
@@ -49,9 +47,7 @@
@error('token') <p class="mt-1 text-sm text-blue-600">{{ $message }}</p> @enderror
@if($hasToken)
<p class="mt-1.5 text-xs text-green-600 flex items-center">
<svg class="w-3.5 h-3.5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
<i class="fa-solid fa-check"></i>
Token sudah tersimpan. Klik <strong>"Ganti"</strong> untuk memperbarui.
</p>
@else
@@ -63,9 +59,7 @@
@if($studentCount > 0)
<div class="p-4 bg-blue-50 border border-blue-200 rounded-xl">
<div class="flex items-center space-x-2">
<svg class="w-5 h-5 text-blue-600 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<i class="fa-solid fa-circle-info"></i>
<div class="text-sm text-blue-700">
<strong>{{ $studentCount }}</strong> siswa sudah tersinkron. Sinkronisasi ulang akan memperbarui data yang sudah ada.
</div>
@@ -76,9 +70,7 @@
{{-- Tombol --}}
<button type="submit"
class="w-full px-4 py-3 text-sm font-medium text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm flex items-center justify-center space-x-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
</svg>
<i class="fa-solid fa-rotate"></i>
<span>Jalankan Sinkronisasi</span>
</button>
</form>
+155 -180
View File
@@ -3,52 +3,49 @@
@section('title', 'Jenis Pelanggaran')
@section('content')
<div x-data="violationTypeManager()" x-init="showImport = false">
<div x-data="violationTypeManager()">
{{-- Header --}}
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 class="text-2xl font-bold text-gray-900">Jenis Pelanggaran</h1>
<p class="text-sm text-gray-500 mt-1">Daftar jenis pelanggaran beserta poin dan sanksi default</p>
</div>
<div class="flex items-center gap-2">
<div class="flex flex-wrap items-center gap-2">
<a href="{{ route('settings.import.template') }}"
class="inline-flex items-center px-3.5 py-2.5 text-sm font-medium text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-gray-300 transition shadow-sm">
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
</svg>
Download Template
class="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-gray-300 transition shadow-sm">
<i class="fa-solid fa-file-import text-xs"></i>
<span class="hidden sm:inline">Download Template</span>
</a>
<button @click="showImport = true"
class="inline-flex items-center px-4 py-2.5 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition shadow-sm">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L5 8m4-4v12"/>
</svg>
Import Excel
<a href="{{ route('settings.export.violation-types') }}"
class="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-gray-300 transition shadow-sm">
<i class="fa-solid fa-file-export text-xs"></i>
<span class="hidden sm:inline">Export Excel</span>
</a>
<button onclick="document.getElementById('import-modal').style.display='flex'"
class="inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition shadow-sm">
<i class="fa-solid fa-download text-xs"></i>
<span>Import Excel</span>
</button>
<button @click="openCreate()"
class="inline-flex items-center px-4 py-2.5 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition shadow-sm">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
</svg>
Tambah Jenis
class="inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition shadow-sm">
<i class="fa-solid fa-plus text-xs"></i>
<span>Tambah Jenis</span>
</button>
</div>
</div>
{{-- Filter & Search Bar --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 mb-6">
<form method="GET" class="p-4">
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
{{-- Filter & Search --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 mb-6">
<form method="GET" class="p-5">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
<div class="relative">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
<i class="fa-solid fa-magnifying-glass absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-400 text-sm pointer-events-none"></i>
<input type="text" name="search" value="{{ request('search') }}"
placeholder="Cari jenis pelanggaran..."
class="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
class="w-full pl-9 pr-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
<select name="category_id"
class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
<option value="">Semua Kategori</option>
@foreach($categories as $cat)
<option value="{{ $cat->id }}" @selected(request('category_id') == $cat->id)>
@@ -56,15 +53,15 @@
</option>
@endforeach
</select>
<div class="flex space-x-2">
<div class="flex items-center gap-2">
<button type="submit"
class="flex-1 px-4 py-2.5 text-sm font-medium text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition">
Filter
class="flex-1 px-4 py-2.5 text-sm font-medium text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm">
<i class="fa-solid fa-filter mr-1.5"></i>Filter
</button>
@if(request()->anyFilled(['search','category_id']))
<a href="{{ route('settings.violation-types') }}"
class="px-4 py-2.5 text-sm font-medium text-gray-600 bg-white border border-gray-300 rounded-xl hover:bg-gray-50 transition">
Reset
class="px-4 py-2.5 text-sm font-medium text-gray-600 bg-white border border-gray-200 rounded-xl hover:bg-gray-50 transition">
<i class="fa-solid fa-xmark mr-1"></i>Reset
</a>
@endif
</div>
@@ -73,76 +70,68 @@
</div>
{{-- Table --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<table class="min-w-full divide-y divide-gray-100">
<thead>
<tr class="bg-gray-50">
<th class="px-5 py-3.5 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">Kategori</th>
<th class="px-5 py-3.5 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">Nama Pelanggaran</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider">Poin</th>
<th class="px-5 py-3.5 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">Sanksi Default</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-5 py-3.5 text-right text-xs font-semibold text-gray-500 uppercase tracking-wider">Aksi</th>
<tr class="bg-gray-50/80">
<th class="px-5 py-3.5 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider">Kategori</th>
<th class="px-5 py-3.5 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider">Nama Pelanggaran</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-gray-400 uppercase tracking-wider">Poin</th>
<th class="px-5 py-3.5 text-left hidden lg:table-cell text-xs font-semibold text-gray-400 uppercase tracking-wider">Sanksi Default</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-gray-400 uppercase tracking-wider">Status</th>
<th class="px-5 py-3.5 text-right text-xs font-semibold text-gray-400 uppercase tracking-wider">Aksi</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
<tbody class="divide-y divide-gray-50">
@forelse($types as $type)
<tr class="hover:bg-gray-50 transition {{ !$type->is_active ? 'opacity-60' : '' }}">
{{-- Kategori --}}
<tr class="hover:bg-gray-50/50 transition {{ !$type->is_active ? 'opacity-50' : '' }}">
<td class="px-5 py-4 whitespace-nowrap">
<div class="flex items-center space-x-2.5">
<span class="w-3 h-3 rounded-full flex-shrink-0" style="background-color: {{ $type->category?->color ?? '#6b7280' }}"></span>
<div class="flex items-center gap-2.5">
<span class="w-2.5 h-2.5 rounded-full flex-shrink-0" style="background-color: {{ $type->category?->color ?? '#6b7280' }}"></span>
<span class="text-sm font-medium" style="color: {{ $type->category?->color ?? '#6b7280' }}">
{{ $type->category?->name ?? '-' }}
</span>
</div>
</td>
{{-- Nama --}}
<td class="px-5 py-4">
<div class="text-sm font-medium text-gray-900 {{ !$type->is_active ? 'line-through' : '' }}">
<span class="text-sm font-medium text-gray-900 {{ !$type->is_active ? 'line-through text-gray-400' : '' }}">
{{ $type->name }}
</div>
</span>
</td>
{{-- Poin --}}
<td class="px-5 py-4 text-center whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-1 text-sm font-bold rounded-lg
@if($type->points >= 50) bg-blue-100 text-red-700
@elseif($type->points >= 15) bg-yellow-100 text-yellow-700
@else bg-green-100 text-green-700
<span class="inline-flex items-center px-2.5 py-1 text-xs font-bold rounded-lg
@if($type->points >= 50) bg-red-50 text-red-700
@elseif($type->points >= 15) bg-yellow-50 text-yellow-700
@else bg-green-50 text-green-700
@endif">
+{{ $type->points }}
</span>
</td>
{{-- Sanksi --}}
<td class="px-5 py-4">
<span class="text-sm text-gray-600 {{ !$type->is_active ? 'line-through' : '' }}">
{{ $type->default_sanction ?: '-' }}
<td class="px-5 py-4 hidden lg:table-cell">
<span class="text-sm text-gray-500 {{ !$type->is_active ? 'line-through' : '' }}">
{{ $type->default_sanction ?: '—' }}
</span>
</td>
{{-- Status --}}
<td class="px-5 py-4 text-center whitespace-nowrap">
@if($type->is_active)
<span class="inline-flex items-center px-2.5 py-0.5 text-xs font-medium bg-green-100 text-green-700 rounded-full">
<span class="w-1.5 h-1.5 bg-green-500 rounded-full mr-1.5"></span>
<span class="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium bg-green-50 text-green-700 border border-green-200 rounded-full">
<span class="w-1.5 h-1.5 bg-green-500 rounded-full"></span>
Aktif
</span>
@else
<span class="inline-flex items-center px-2.5 py-0.5 text-xs font-medium bg-gray-100 text-gray-500 rounded-full">
<span class="w-1.5 h-1.5 bg-gray-400 rounded-full mr-1.5"></span>
<span class="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium bg-gray-100 text-gray-500 border border-gray-200 rounded-full">
<span class="w-1.5 h-1.5 bg-gray-400 rounded-full"></span>
Nonaktif
</span>
@endif
</td>
{{-- Aksi --}}
<td class="px-5 py-4 text-right whitespace-nowrap">
<div class="flex items-center justify-end space-x-2">
<div class="flex items-center justify-end gap-1.5">
<button @click="openEdit({{ $type->id }}, {{ $type->category_id }}, '{{ addslashes($type->name) }}', {{ $type->points }}, '{{ addslashes($type->default_sanction ?? '') }}', {{ $type->is_active ? 'true' : 'false' }})"
class="inline-flex items-center px-3 py-1.5 text-xs font-medium text-blue-600 bg-blue-50 border border-blue-200 rounded-lg hover:bg-blue-100 transition">
<svg class="w-3.5 h-3.5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
Edit
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-blue-600 bg-blue-50 border border-blue-200 rounded-lg hover:bg-blue-100 transition">
<i class="fa-solid fa-pen-to-square"></i>
<span class="hidden sm:inline">Edit</span>
</button>
@if($type->is_active)
<form action="{{ route('settings.violation-types.update', $type->id) }}" method="POST" class="inline">
@@ -153,8 +142,9 @@
<input type="hidden" name="default_sanction" value="{{ $type->default_sanction }}">
<input type="hidden" name="is_active" value="0">
<button type="submit"
class="inline-flex items-center px-3 py-1.5 text-xs font-medium text-yellow-600 bg-yellow-50 border border-yellow-200 rounded-lg hover:bg-yellow-100 transition">
Nonaktifkan
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-yellow-600 bg-yellow-50 border border-yellow-200 rounded-lg hover:bg-yellow-100 transition">
<i class="fa-solid fa-ban"></i>
<span class="hidden sm:inline">Nonaktifkan</span>
</button>
</form>
@else
@@ -166,19 +156,18 @@
<input type="hidden" name="default_sanction" value="{{ $type->default_sanction }}">
<input type="hidden" name="is_active" value="1">
<button type="submit"
class="inline-flex items-center px-3 py-1.5 text-xs font-medium text-green-600 bg-green-50 border border-green-200 rounded-lg hover:bg-green-100 transition">
Aktifkan
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-green-600 bg-green-50 border border-green-200 rounded-lg hover:bg-green-100 transition">
<i class="fa-solid fa-check"></i>
<span class="hidden sm:inline">Aktifkan</span>
</button>
</form>
@endif
<form action="{{ route('settings.violation-types.destroy', $type->id) }}" method="POST" class="inline"
x-data x-on:submit.prevent="if(await window.confirmSwal({text:'Hapus jenis pelanggaran ini?'})) $el.submit()"">
x-data x-on:submit.prevent="if(await window.confirmSwal({text:'Hapus jenis pelanggaran ini?'})) $el.submit()">
@csrf @method('DELETE')
<button type="submit"
class="inline-flex items-center px-3 py-1.5 text-xs font-medium text-blue-600 bg-red-50 border border-blue-200 rounded-lg hover:bg-blue-100 transition">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
class="inline-flex items-center justify-center w-8 h-8 text-xs font-medium text-red-500 bg-red-50 border border-red-200 rounded-lg hover:bg-red-100 transition">
<i class="fa-solid fa-trash-can text-xs"></i>
</button>
</form>
</div>
@@ -186,12 +175,14 @@
</tr>
@empty
<tr>
<td colspan="6" class="px-5 py-16 text-center">
<svg class="mx-auto h-12 w-12 text-gray-300 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/>
</svg>
<p class="text-sm text-gray-500 mb-1">Tidak ada jenis pelanggaran ditemukan</p>
<p class="text-xs text-gray-400">Coba ubah filter atau tambah jenis pelanggaran baru</p>
<td colspan="6" class="px-5 py-20 text-center">
<div class="flex flex-col items-center">
<div class="w-14 h-14 rounded-full bg-gray-50 border border-gray-100 flex items-center justify-center mb-4">
<i class="fa-solid fa-list text-gray-300 text-xl"></i>
</div>
<p class="text-sm font-medium text-gray-500 mb-1">Tidak ada jenis pelanggaran</p>
<p class="text-xs text-gray-400">Coba ubah filter atau tambah jenis pelanggaran baru</p>
</div>
</td>
</tr>
@endforelse
@@ -199,47 +190,39 @@
</table>
</div>
{{-- Pagination --}}
@if($types->hasPages())
<div class="px-5 py-3 border-t border-gray-100 bg-gray-50">
<div class="px-5 py-3 border-t border-gray-100 bg-gray-50/50">
{{ $types->appends(request()->query())->links() }}
</div>
@endif
{{-- Summary --}}
<div class="px-5 py-3 border-t border-gray-100 bg-gray-50 flex items-center justify-between text-xs text-gray-500">
<div class="px-5 py-3 border-t border-gray-100 bg-gray-50/50 flex items-center justify-between text-xs text-gray-400">
<span>Menampilkan {{ $types->firstItem() ?? 0 }}–{{ $types->lastItem() ?? 0 }} dari {{ $types->total() }} jenis</span>
<span class="font-medium">{{ $types->total() }} total</span>
</div>
</div>
{{-- Modal Tambah / Edit --}}
{{-- ===== CRUD MODAL ===== --}}
<div x-show="modalOpen" class="fixed inset-0 z-50 overflow-y-auto" style="display: none;">
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:p-0">
<div x-show="modalOpen" @click="modalOpen = false" class="fixed inset-0 bg-gray-500 bg-opacity-50 transition-opacity"></div>
<div x-show="modalOpen" class="fixed inset-0 bg-gray-900/40 backdrop-blur-sm"></div>
<div x-show="modalOpen" @click.away="modalOpen = false"
<div x-show="modalOpen"
class="relative inline-block align-bottom bg-white rounded-2xl shadow-xl border border-gray-200 text-left overflow-hidden transform transition-all sm:align-middle sm:max-w-xl sm:w-full">
{{-- Header --}}
<div class="px-6 py-5 border-b border-gray-100 flex items-center justify-between">
<div class="flex items-center space-x-3">
<div class="w-10 h-10 rounded-lg flex items-center justify-center" :class="isEditing ? 'bg-blue-100' : 'bg-blue-100'">
<svg x-show="!isEditing" class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
</svg>
<svg x-show="isEditing" class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-blue-50 flex items-center justify-center">
<i x-show="!isEditing" class="fa-solid fa-plus text-blue-600"></i>
<i x-show="isEditing" class="fa-solid fa-pen-to-square text-blue-600"></i>
</div>
<div>
<h3 class="text-lg font-semibold text-gray-900" x-text="isEditing ? 'Edit Jenis Pelanggaran' : 'Tambah Jenis Pelanggaran'"></h3>
<p class="text-sm text-gray-500" x-text="isEditing ? 'Ubah detail jenis pelanggaran' : 'Buat jenis pelanggaran baru'"></p>
<h3 class="text-base font-semibold text-gray-900" x-text="isEditing ? 'Edit Jenis Pelanggaran' : 'Tambah Jenis Pelanggaran'"></h3>
<p class="text-xs text-gray-400" x-text="isEditing ? 'Ubah detail jenis pelanggaran' : 'Buat jenis pelanggaran baru'"></p>
</div>
</div>
<button @click="modalOpen = false" class="text-gray-400 hover:text-gray-600 transition">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
<button @click="modalOpen = false" class="w-8 h-8 rounded-lg flex items-center justify-center text-gray-400 hover:text-gray-600 hover:bg-gray-100 transition">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
@@ -249,46 +232,50 @@
@csrf
<input type="hidden" name="_method" :value="isEditing ? 'PUT' : 'POST'">
{{-- Kategori --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5">Kategori <span class="text-red-500">*</span></label>
<select x-model="formCategory" @change="updateCategoryColor(formCategory)" name="category_id" required
class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
<option value="">-- Pilih Kategori --</option>
@foreach($categories as $cat)
<option value="{{ $cat->id }}">{{ $cat->name }}</option>
@endforeach
</select>
</div>
{{-- Nama --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5">Nama Pelanggaran <span class="text-red-500">*</span></label>
<input type="text" x-model="formName" name="name" required
class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition"
placeholder="Contoh: Terlambat datang ke sekolah">
</div>
{{-- Poin + Sanksi --}}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
{{-- Kategori --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5">Kategori <span class="text-red-500">*</span></label>
<select x-model="formCategory" @change="updateCategoryColor(formCategory)" name="category_id" required
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
<option value="">-- Pilih Kategori --</option>
@foreach($categories as $cat)
<option value="{{ $cat->id }}">{{ $cat->name }}</option>
@endforeach
</select>
</div>
{{-- Nama --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5">Nama Pelanggaran <span class="text-red-500">*</span></label>
<input type="text" x-model="formName" name="name" required
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition"
placeholder="Contoh: Terlambat datang ke sekolah">
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
{{-- Poin --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5">Poin <span class="text-red-500">*</span></label>
<input type="number" x-model="formPoints" name="points" required min="0" max="500"
class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
<p class="text-xs text-gray-400 mt-1">Poin pelanggaran (1–500)</p>
</div>
{{-- Sanksi --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5">Sanksi Default</label>
<input type="text" x-model="formSanction" name="default_sanction"
class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition"
placeholder="Contoh: Teguran lisan">
<p class="text-xs text-gray-400 mt-1">Sanksi yang muncul saat input pelanggaran</p>
</div>
</div>
{{-- Status toggle (edit only) --}}
<div x-show="isEditing" class="flex items-center justify-between p-4 bg-gray-50 rounded-xl border border-gray-100">
<div>
<p class="text-sm font-medium text-gray-900">Status Aktif</p>
<p class="text-xs text-gray-500">Nonaktifkan untuk menyembunyikan jenis pelanggaran ini</p>
<p class="text-sm font-semibold text-gray-900">Status Aktif</p>
<p class="text-xs text-gray-400 mt-0.5">Nonaktifkan untuk menyembunyikan dari daftar pemilihan</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" x-model="formActive" name="is_active" value="1" class="sr-only peer">
@@ -298,62 +285,51 @@
{{-- Preview --}}
<div class="p-4 bg-gray-50 rounded-xl border border-gray-100">
<p class="text-xs font-medium text-gray-500 mb-2">Pratinjau</p>
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider mb-3">Pratinjau</p>
<div class="flex items-center justify-between p-3 bg-white rounded-lg border border-gray-200">
<div class="flex items-center space-x-2.5">
<span class="w-3 h-3 rounded-full flex-shrink-0"
:style="{ backgroundColor: categoryColor }"></span>
<span class="text-sm font-medium text-gray-900" x-text="formName || 'Nama Pelanggaran'"></span>
<div class="flex items-center gap-2.5 min-w-0">
<span class="w-2.5 h-2.5 rounded-full flex-shrink-0" :style="{ backgroundColor: categoryColor }"></span>
<span class="text-sm font-medium text-gray-900 truncate" x-text="formName || 'Nama Pelanggaran'"></span>
</div>
<span class="text-sm font-bold text-blue-600" x-text="'+' + (formPoints || 0)"></span>
<span class="text-sm font-bold flex-shrink-0 ml-3" :class="formPoints >= 50 ? 'text-red-600' : formPoints >= 15 ? 'text-yellow-600' : 'text-green-600'" x-text="'+' + (formPoints || 0)"></span>
</div>
<p class="text-xs text-gray-400 mt-1.5" x-text="formSanction ? 'Sanksi: ' + formSanction : ''"></p>
<p class="text-xs text-gray-400 mt-2" x-text="formSanction ? 'Sanksi: ' + formSanction : ''"></p>
</div>
{{-- Buttons --}}
<div class="flex justify-end space-x-3 pt-2">
<div class="flex justify-end gap-3 pt-2 border-t border-gray-100">
<button type="button" @click="modalOpen = false"
class="px-5 py-2.5 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-xl hover:bg-gray-50 transition">
class="px-5 py-2.5 text-sm font-medium text-gray-600 bg-white border border-gray-200 rounded-xl hover:bg-gray-50 transition">
Batal
</button>
<button type="submit"
class="px-5 py-2.5 text-sm font-medium text-white rounded-xl transition shadow-sm"
:class="isEditing ? 'bg-blue-600 hover:bg-blue-700' : 'bg-blue-600 hover:bg-blue-700'"
x-text="isEditing ? 'Simpan Perubahan' : 'Tambah Jenis'">
class="inline-flex items-center gap-2 px-5 py-2.5 text-sm font-semibold text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm">
<i class="fa-solid" :class="isEditing ? 'fa-floppy-disk' : 'fa-plus'"></i>
<span x-text="isEditing ? 'Simpan Perubahan' : 'Tambah Jenis'"></span>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{{-- Import Modal --}}
<div x-show="showImport"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/40 backdrop-blur-sm" style="display: none;"
@click.away="showImport = false"
@keydown.escape="showImport = false">
<div class="bg-white rounded-2xl shadow-2xl border border-gray-200 w-full max-w-lg mx-4 overflow-hidden"
@click.stop>
{{-- ===== IMPORT MODAL ===== --}}
<div id="import-modal" style="display: none;"
class="fixed inset-0 z-50 flex-col items-center justify-center bg-gray-900/40 backdrop-blur-sm"
tabindex="0">
<div class="bg-white rounded-2xl shadow-2xl border border-gray-200 w-full max-w-lg mx-4 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between">
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-lg bg-blue-50 flex items-center justify-center">
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L5 8m4-4v12"/>
</svg>
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-blue-50 flex items-center justify-center">
<i class="fa-solid fa-download text-blue-600"></i>
</div>
<div>
<h3 class="text-base font-semibold text-gray-900">Import Excel</h3>
<p class="text-xs text-gray-400">Upload file .xlsx jenis pelanggaran</p>
</div>
<h3 class="text-base font-semibold text-gray-900">Import Excel</h3>
</div>
<button @click="showImport = false" class="text-gray-400 hover:text-gray-600 transition">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
<button onclick="document.getElementById('import-modal').style.display='none'" class="w-8 h-8 rounded-lg flex items-center justify-center text-gray-400 hover:text-gray-600 hover:bg-gray-100 transition">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
@@ -361,11 +337,11 @@
@csrf
<div class="text-sm text-gray-600 mb-4 leading-relaxed">
<p class="mb-2">Upload file Excel (.xlsx) untuk mengimpor jenis pelanggaran secara massal.</p>
<p class="mb-2">Upload file Excel untuk mengimpor jenis pelanggaran secara massal.</p>
<p class="text-xs text-gray-400">Format kolom: <strong>Kategori</strong> • <strong>Nama Pelanggaran</strong> • <strong>Poin</strong> • <strong>Sanksi Default</strong> • <strong>Deskripsi</strong></p>
</div>
<label class="flex flex-col items-center justify-center h-36 border-2 border-dashed border-gray-200 rounded-xl cursor-pointer hover:border-blue-300 hover:bg-blue-50/30 transition file-drop"
<label class="flex flex-col items-center justify-center h-36 border-2 border-dashed border-gray-200 rounded-xl cursor-pointer hover:border-blue-300 hover:bg-blue-50/30 transition"
x-data="{ fileName: '' }"
@dragover.prevent="$el.classList.add('border-blue-400', 'bg-blue-50/50')"
@dragleave.prevent="$el.classList.remove('border-blue-400', 'bg-blue-50/50')"
@@ -375,18 +351,14 @@
<template x-if="!fileName">
<div class="text-center">
<svg class="w-8 h-8 text-gray-300 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/>
</svg>
<i class="fa-solid fa-cloud-arrow-up text-3xl text-gray-300 mb-3"></i>
<p class="text-sm text-gray-500">Klik atau drag & drop file di sini</p>
<p class="text-xs text-gray-400 mt-1">.xlsx atau .xls, maks 2 MB</p>
</div>
</template>
<template x-if="fileName">
<div class="text-center">
<svg class="w-8 h-8 text-green-500 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<i class="fa-solid fa-circle-check text-3xl text-green-500 mb-3"></i>
<p class="text-sm font-medium text-gray-900" x-text="fileName"></p>
<p class="text-xs text-green-600 mt-1">Siap diupload</p>
</div>
@@ -394,16 +366,19 @@
</label>
<div class="flex items-center justify-between mt-6">
<a href="{{ route('settings.import.template') }}" class="text-xs text-blue-600 hover:text-blue-800 font-medium transition">
Download template →
<a href="{{ route('settings.import.template') }}"
class="inline-flex items-center gap-1.5 text-xs font-medium text-blue-600 hover:text-blue-800 transition">
<i class="fa-solid fa-file-import"></i>
Download template
</a>
<div class="flex items-center gap-2">
<button type="button" @click="showImport = false"
<button type="button" onclick="document.getElementById('import-modal').style.display='none'"
class="px-4 py-2 text-sm font-medium text-gray-600 border border-gray-200 rounded-lg hover:bg-gray-50 transition">
Batal
</button>
<button type="submit"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition shadow-sm">
class="inline-flex items-center gap-1.5 px-4 py-2 text-sm font-semibold text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition shadow-sm">
<i class="fa-solid fa-cloud-arrow-up text-xs"></i>
Import
</button>
</div>
+95 -73
View File
@@ -5,15 +5,16 @@
@section('content')
<div>
{{-- Header --}}
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 class="text-2xl font-bold text-gray-900">Data Siswa</h1>
<h1 class="text-2xl font-bold text-gray-900 tracking-tight">Data Siswa</h1>
<p class="text-sm text-gray-500 mt-1">Daftar siswa aktif — sinkron dari Database Kesiswaan</p>
</div>
<div class="flex items-center space-x-2 text-sm">
<span class="text-gray-500">{{ $students->total() }} siswa</span>
<div class="flex items-center gap-2">
<span class="text-sm text-gray-500 font-medium">{{ $students->total() }} siswa</span>
@if(request()->anyFilled(['search','class_level','class_name','department']))
<span class="inline-flex items-center px-2.5 py-1 text-xs font-medium bg-blue-100 text-red-700 rounded-full">
<span class="inline-flex items-center gap-1 px-2.5 py-1 text-xs font-medium bg-blue-50 text-blue-700 rounded-full">
<i class="fa-solid fa-filter text-xs"></i>
{{ $students->total() }} ditemukan
</span>
@endif
@@ -21,28 +22,23 @@
</div>
{{-- Filter Card --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 mb-6">
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 mb-6">
<form method="GET">
<div class="p-5 space-y-4">
{{-- Baris 1: Search --}}
<div>
<label class="block text-xs font-medium text-gray-500 mb-1.5">Pencarian</label>
<div class="relative">
<svg class="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
<input type="text" name="search" value="{{ request('search') }}"
placeholder="Cari NISN, NIS, atau Nama siswa..."
class="w-full pl-10 pr-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
{{-- Search --}}
<div class="relative">
<i class="fa-solid fa-magnifying-glass absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-400 text-sm pointer-events-none"></i>
<input type="text" name="search" value="{{ request('search') }}"
placeholder="Cari NISN, NIS, atau Nama siswa..."
class="w-full pl-9 pr-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
{{-- Baris 2: Dropdown --}}
{{-- Dropdowns --}}
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
<div>
<label class="block text-xs font-medium text-gray-500 mb-1.5">Tingkat Kelas</label>
<label class="block text-[11px] font-semibold text-gray-400 uppercase tracking-wider mb-1.5">Tingkat Kelas</label>
<select name="class_level"
class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition bg-white">
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
<option value="">Semua Tingkat</option>
@foreach($classLevels as $level)
<option value="{{ $level }}" @selected(request('class_level') == $level)>Kelas {{ $level }}</option>
@@ -50,9 +46,9 @@
</select>
</div>
<div>
<label class="block text-xs font-medium text-gray-500 mb-1.5">Kelas</label>
<label class="block text-[11px] font-semibold text-gray-400 uppercase tracking-wider mb-1.5">Kelas</label>
<select name="class_name"
class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition bg-white">
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
<option value="">Semua Kelas</option>
@foreach($classNames as $cn)
<option value="{{ $cn }}" @selected(request('class_name') == $cn)>{{ $cn }}</option>
@@ -60,30 +56,26 @@
</select>
</div>
<div>
<label class="block text-xs font-medium text-gray-500 mb-1.5">Jurusan</label>
<label class="block text-[11px] font-semibold text-gray-400 uppercase tracking-wider mb-1.5">Jurusan</label>
<select name="department"
class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition bg-white">
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
<option value="">Semua Jurusan</option>
@foreach($departments as $code => $name)
<option value="{{ $code }}" @selected(request('department') == $code)>{{ $name }} ({{ $code }})</option>
@endforeach
</select>
</div>
<div class="flex items-end space-x-2">
<div class="flex items-end gap-2">
<button type="submit"
class="flex-1 px-4 py-2.5 text-sm font-medium text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm flex items-center justify-center space-x-1.5">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
<span>Cari</span>
class="flex-1 px-4 py-2.5 text-sm font-medium text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm inline-flex items-center justify-center gap-1.5">
<i class="fa-solid fa-magnifying-glass text-xs"></i>
Cari
</button>
@if(request()->anyFilled(['search','class_level','class_name','department']))
<a href="{{ route('students.index') }}"
class="px-4 py-2.5 text-sm font-medium text-gray-600 bg-white border border-gray-300 rounded-xl hover:bg-gray-50 transition flex items-center space-x-1">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
<span>Reset</span>
class="px-4 py-2.5 text-sm font-medium text-gray-600 bg-white border border-gray-200 rounded-xl hover:bg-gray-50 transition inline-flex items-center gap-1.5">
<i class="fa-solid fa-xmark"></i>
Reset
</a>
@endif
</div>
@@ -93,64 +85,92 @@
</div>
{{-- Table --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3.5 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">NISN</th>
<th class="px-4 py-3.5 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">Nama</th>
<th class="px-4 py-3.5 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">Kelas</th>
<th class="px-4 py-3.5 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">Jurusan</th>
<th class="px-4 py-3.5 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider">Pelanggaran</th>
<th class="px-4 py-3.5 text-center text-xs font-semibold text-gray-500 uppercase tracking-wider">Total Poin</th>
<th class="px-4 py-3.5 text-right text-xs font-semibold text-gray-500 uppercase tracking-wider">Aksi</th>
<table class="min-w-full divide-y divide-gray-100">
<thead>
<tr class="bg-gray-50/80">
<th class="px-5 py-3.5 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider">Siswa</th>
<th class="px-5 py-3.5 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider">NISN</th>
<th class="px-5 py-3.5 text-left hidden sm:table-cell text-xs font-semibold text-gray-400 uppercase tracking-wider">Kelas</th>
<th class="px-5 py-3.5 text-left hidden lg:table-cell text-xs font-semibold text-gray-400 uppercase tracking-wider">Jurusan</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-gray-400 uppercase tracking-wider">Pelanggaran</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-gray-400 uppercase tracking-wider">Total Poin</th>
<th class="px-5 py-3.5 text-right text-xs font-semibold text-gray-400 uppercase tracking-wider">Aksi</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
<tbody class="divide-y divide-gray-50">
@forelse($students as $s)
@php $pts = $s->total_points; @endphp
<tr class="hover:bg-gray-50 transition">
<td class="px-4 py-3.5 whitespace-nowrap text-sm font-mono text-gray-700">{{ $s->nisn }}</td>
<td class="px-4 py-3.5">
<div class="text-sm font-medium text-gray-900">{{ $s->full_name }}</div>
<tr class="hover:bg-gray-50/50 transition">
{{-- Siswa --}}
<td class="px-5 py-4">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center text-white text-sm font-bold shadow-sm flex-shrink-0">
{{ strtoupper(substr($s->full_name, 0, 1)) }}
</div>
<div class="min-w-0">
<p class="text-sm font-semibold text-gray-900 truncate max-w-[200px]">{{ $s->full_name }}</p>
<div class="flex flex-wrap items-center gap-1 mt-0.5">
<span class="inline-flex items-center px-1.5 py-0.5 text-[10px] font-mono font-medium bg-gray-100 text-gray-500 rounded-md">{{ $s->nisn ?? '—' }}</span>
</div>
</div>
</div>
</td>
<td class="px-4 py-3.5 whitespace-nowrap text-sm text-gray-700">{{ $s->class_name ?? '-' }}</td>
<td class="px-4 py-3.5 text-sm text-gray-500">{{ $s->department_code ?? '-' }}</td>
<td class="px-4 py-3.5 text-center text-sm text-gray-700">
<span class="inline-flex items-center px-2 py-0.5 text-xs font-medium rounded-full bg-gray-100 text-gray-600">
{{-- NISN --}}
<td class="px-5 py-4 whitespace-nowrap">
<span class="text-sm font-mono text-gray-700">{{ $s->nisn ?? '—' }}</span>
</td>
{{-- Kelas --}}
<td class="px-5 py-4 whitespace-nowrap hidden sm:table-cell">
<span class="inline-flex items-center gap-1.5 px-3 py-1 text-xs font-medium bg-blue-50 text-blue-700 rounded-full border border-blue-200">
<i class="fa-solid fa-building text-[10px]"></i>
{{ $s->class_name ?? '—' }}
</span>
</td>
{{-- Jurusan --}}
<td class="px-5 py-4 hidden lg:table-cell">
<span class="text-sm text-gray-500">{{ $s->department_code ?? '—' }}</span>
</td>
{{-- Pelanggaran --}}
<td class="px-5 py-4 text-center whitespace-nowrap">
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-full
{{ ($s->violations_count ?? 0) > 0 ? 'bg-orange-50 text-orange-700 border border-orange-200' : 'bg-gray-50 text-gray-400 border border-gray-200' }}">
<i class="fa-solid {{ ($s->violations_count ?? 0) > 0 ? 'fa-exclamation' : 'fa-check' }} text-[10px]"></i>
{{ $s->violations_count ?? 0 }}x
</span>
</td>
<td class="px-4 py-3.5 text-center">
{{-- Total Poin --}}
<td class="px-5 py-4 text-center whitespace-nowrap">
@if($pts >= 100)
<span class="inline-flex px-3 py-1 text-xs font-bold rounded-full bg-blue-100 text-blue-800">{{ $pts }}</span>
<span class="inline-flex items-center gap-1 px-3 py-1 text-xs font-bold bg-red-50 text-red-700 rounded-full">{{ $pts }}</span>
@elseif($pts >= 50)
<span class="inline-flex px-3 py-1 text-xs font-bold rounded-full bg-yellow-100 text-yellow-800">{{ $pts }}</span>
<span class="inline-flex items-center gap-1 px-3 py-1 text-xs font-bold bg-yellow-50 text-yellow-700 rounded-full">{{ $pts }}</span>
@elseif($pts > 0)
<span class="inline-flex px-3 py-1 text-xs font-bold rounded-full bg-orange-100 text-orange-700">{{ $pts }}</span>
<span class="inline-flex items-center gap-1 px-3 py-1 text-xs font-bold bg-orange-50 text-orange-600 rounded-full">{{ $pts }}</span>
@else
<span class="inline-flex px-3 py-1 text-xs rounded-full bg-green-100 text-green-700">0</span>
<span class="inline-flex items-center gap-1 px-3 py-1 text-xs bg-green-50 text-green-600 rounded-full">0</span>
@endif
</td>
<td class="px-4 py-3.5 text-right">
{{-- Aksi --}}
<td class="px-5 py-4 text-right whitespace-nowrap">
<a href="{{ route('students.show', $s->id) }}"
class="inline-flex items-center px-3 py-1.5 text-sm font-medium text-blue-600 bg-red-50 border border-blue-200 rounded-lg hover:bg-blue-100 transition">
<svg class="w-3.5 h-3.5 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
</svg>
class="inline-flex items-center gap-1.5 px-3.5 py-1.5 text-xs font-medium text-blue-600 bg-blue-50 border border-blue-200 rounded-lg hover:bg-blue-100 transition">
<i class="fa-solid fa-eye"></i>
Detail
</a>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="px-4 py-16 text-center">
<svg class="mx-auto h-12 w-12 text-gray-300 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zM6 10h.01M18 10h.01"/>
</svg>
<p class="text-sm text-gray-500 mb-1">Tidak ada siswa ditemukan</p>
<p class="text-xs text-gray-400">Coba ubah filter pencarian atau lakukan sinkronisasi data</p>
<td colspan="7" class="px-5 py-20 text-center">
<div class="flex flex-col items-center">
<div class="w-14 h-14 rounded-full bg-gray-50 border border-gray-100 flex items-center justify-center mb-4">
<i class="fa-solid fa-users-slash text-gray-300 text-xl"></i>
</div>
<p class="text-sm font-medium text-gray-500 mb-1">Tidak ada siswa ditemukan</p>
<p class="text-xs text-gray-400">Coba ubah filter atau lakukan sinkronisasi data</p>
</div>
</td>
</tr>
@endforelse
@@ -158,13 +178,15 @@
</table>
</div>
{{-- Pagination --}}
@if($students->hasPages())
<div class="px-4 py-3 border-t border-gray-100 bg-gray-50">
<div class="px-5 py-3 border-t border-gray-100 bg-gray-50/50">
{{ $students->appends(request()->query())->links() }}
</div>
@endif
<div class="px-4 py-3 border-t border-gray-100 bg-gray-50 flex items-center justify-between text-xs text-gray-500">
{{-- Summary --}}
<div class="px-5 py-3 border-t border-gray-100 bg-gray-50/50 flex items-center justify-between text-xs text-gray-400">
<span>Menampilkan {{ $students->firstItem() ?? 0 }}–{{ $students->lastItem() ?? 0 }} dari {{ $students->total() }} siswa</span>
<span class="font-medium">{{ $students->total() }} total</span>
</div>
+394 -211
View File
@@ -7,26 +7,15 @@
.violation-timeline::before {
content: '';
position: absolute;
left: 20px;
top: 8px;
bottom: 8px;
left: 23px;
top: 12px;
bottom: 12px;
width: 2px;
background: #e5e7eb;
}
.sp-progress-bar {
background: linear-gradient(90deg, #dbeafe 0%, #93c5fd 40%, #60a5fa 70%, #3b82f6 100%);
}
.perspective-card {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.perspective-card:hover {
transform: translateY(-2px);
box-shadow: 0 12px 24px -8px rgba(0, 0, 0, 0.08);
}
.glass-stat {
background: linear-gradient(135deg, rgba(255,255,255,0.9), rgba(255,255,255,0.98));
backdrop-filter: blur(8px);
}
</style>
@endpush
@@ -35,28 +24,25 @@
{{-- Header --}}
<div class="mb-6 flex items-center justify-between">
<div>
<nav class="flex items-center space-x-2 text-sm text-gray-400 mb-2">
<nav class="flex items-center gap-1.5 text-sm text-gray-400 mb-1">
<a href="{{ route('students.index') }}" class="hover:text-gray-600 transition">Data Siswa</a>
<span>/</span>
<span class="text-gray-300">/</span>
<span class="text-gray-700 font-medium truncate max-w-[200px]">{{ $student->full_name }}</span>
</nav>
<h1 class="text-2xl font-bold text-gray-900 tracking-tight">Profil Siswa</h1>
<h1 class="text-xl sm:text-2xl font-bold text-gray-900 tracking-tight">Profil Siswa</h1>
</div>
<a href="{{ route('students.index') }}"
class="inline-flex items-center px-4 py-2.5 text-sm font-medium text-gray-600 bg-white border border-gray-200 rounded-xl hover:bg-gray-50 hover:border-gray-300 transition-all shadow-sm">
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
</svg>
class="inline-flex items-center gap-1.5 px-3.5 py-2 text-sm font-medium text-gray-600 bg-white border border-gray-200 rounded-xl hover:bg-gray-50 hover:border-gray-300 transition shadow-sm">
<i class="fa-solid fa-arrow-left text-xs"></i>
Kembali
</a>
</div>
{{-- ===== HERO CARD ===== --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6 perspective-card">
<div class="bg-gradient-to-r from-blue-500 via-blue-400 to-sky-300 h-28 sm:h-36"></div>
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
<div class="bg-gradient-to-r from-blue-500 via-blue-400 to-sky-300 h-24 sm:h-32"></div>
<div class="relative px-6 pb-6">
{{-- Avatar --}}
<div class="flex flex-col sm:flex-row sm:items-end -mt-14 sm:-mt-20 mb-4 gap-4">
<div class="flex flex-col sm:flex-row sm:items-end -mt-12 sm:-mt-16 mb-5 gap-4">
<div class="w-20 h-20 sm:w-24 sm:h-24 rounded-2xl bg-white shadow-lg border-2 border-white flex items-center justify-center flex-shrink-0">
@if($student->photo_path)
<img src="{{ Storage::url($student->photo_path) }}" alt="{{ $student->full_name }}"
@@ -66,20 +52,20 @@
@endif
</div>
<div class="flex-1 min-w-0 pt-5 sm:pt-0">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
<div>
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-extrabold text-gray-900 truncate leading-tight tracking-tight">{{ $student->full_name }}</h2>
<h2 class="text-2xl sm:text-3xl lg:text-4xl font-bold text-gray-900 truncate leading-tight tracking-tight">{{ $student->full_name }}</h2>
<div class="flex flex-wrap items-center gap-2 mt-3">
<span class="inline-flex items-center gap-2 px-3.5 py-1.5 text-sm font-mono font-semibold bg-gray-50 text-blue-700 border border-blue-200 rounded-xl">
<svg class="w-4 h-4 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2h-5m-4 0V5a2 2 0 114 0v1m-4 0a2 2 0 104 0"/></svg>
<span class="inline-flex items-center gap-1.5 px-3.5 py-1.5 text-xs font-mono font-semibold bg-gray-100 text-blue-700 rounded-full border border-blue-200">
<i class="fa-solid fa-id-card text-blue-400"></i>
{{ $student->nisn ?? '—' }}
</span>
<span class="inline-flex items-center gap-2 px-3.5 py-1.5 text-sm font-medium bg-blue-50 text-blue-700 border border-blue-200 rounded-xl">
<svg class="w-4 h-4 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/></svg>
<span class="inline-flex items-center gap-1.5 px-3.5 py-1.5 text-xs font-medium bg-emerald-50 text-emerald-700 rounded-full border border-emerald-200">
<i class="fa-solid fa-building text-emerald-400"></i>
{{ $student->class_name ?? '—' }}
</span>
<span class="inline-flex items-center gap-2 px-3.5 py-1.5 text-sm font-medium bg-blue-50 text-blue-700 border border-blue-200 rounded-xl">
<svg class="w-4 h-4 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"/></svg>
<span class="inline-flex items-center gap-1.5 px-3.5 py-1.5 text-xs font-medium bg-purple-50 text-purple-700 rounded-full border border-purple-200">
<i class="fa-solid fa-graduation-cap text-purple-400"></i>
{{ $student->department_name ?? $student->department_code ?? '—' }}
</span>
</div>
@@ -105,37 +91,41 @@
@if($currentSpLevel)
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-semibold mb-4"
style="background-color: {{ $currentSpLevel->color }}15; color: {{ $currentSpLevel->color }}; border: 1px solid {{ $currentSpLevel->color }}30;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<i class="fa-solid fa-triangle-exclamation"></i>
{{ $currentSpLevel->name }} — {{ $currentSpLevel->min_points }}+ poin
</div>
@endif
{{-- Quick Stats --}}
{{-- Gradient Stat Cards --}}
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
<div class="glass-stat rounded-xl border border-gray-100 p-4 text-center">
<p class="text-xs font-medium text-gray-400 uppercase tracking-wider mb-1">Total Poin</p>
<p class="text-2xl font-bold {{ $totalPoints >= 100 ? 'text-blue-600' : ($totalPoints >= 50 ? 'text-blue-500' : ($totalPoints > 0 ? 'text-blue-400' : 'text-gray-400')) }}">
{{ $totalPoints }}
</p>
<div class="relative overflow-hidden rounded-xl bg-gradient-to-br from-blue-500 to-blue-600 p-4 shadow-sm">
<div class="absolute right-0 top-0 w-12 h-12 opacity-15">
<svg class="w-full h-full text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
</div>
<p class="text-[10px] font-semibold text-white/70 uppercase tracking-wider">Total Poin</p>
<p class="text-xl font-bold text-white mt-1">{{ $totalPoints }}</p>
</div>
<div class="glass-stat rounded-xl border border-gray-100 p-4 text-center">
<p class="text-xs font-medium text-gray-400 uppercase tracking-wider mb-1">Pelanggaran</p>
<p class="text-2xl font-bold text-gray-800">{{ $violationCount }}<span class="text-sm font-medium text-gray-400">x</span></p>
<div class="relative overflow-hidden rounded-xl bg-gradient-to-br from-orange-500 to-red-600 p-4 shadow-sm">
<div class="absolute right-0 top-0 w-12 h-12 opacity-15">
<svg class="w-full h-full text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
</div>
<p class="text-[10px] font-semibold text-white/70 uppercase tracking-wider">Pelanggaran</p>
<p class="text-xl font-bold text-white mt-1">{{ $violationCount }}<span class="text-sm font-medium text-white/60">x</span></p>
</div>
<div class="glass-stat rounded-xl border border-gray-100 p-4 text-center">
<p class="text-xs font-medium text-gray-400 uppercase tracking-wider mb-1">SP Terbit</p>
<p class="text-2xl font-bold {{ $activeSpLetters > 0 ? 'text-blue-600' : 'text-gray-400' }}">{{ $activeSpLetters }}</p>
<div class="relative overflow-hidden rounded-xl bg-gradient-to-br from-violet-500 to-violet-700 p-4 shadow-sm">
<div class="absolute right-0 top-0 w-12 h-12 opacity-15">
<svg class="w-full h-full text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>
</div>
<p class="text-[10px] font-semibold text-white/70 uppercase tracking-wider">SP Terbit</p>
<p class="text-xl font-bold text-white mt-1">{{ $activeSpLetters }}</p>
</div>
<div class="glass-stat rounded-xl border border-gray-100 p-4 text-center">
<p class="text-xs font-medium text-gray-400 uppercase tracking-wider mb-1">Terakhir</p>
<p class="text-sm font-semibold text-gray-700 mt-1">
@if($lastViolation)
{{ $lastViolation->violation_date->diffForHumans() }}
@else
—
@endif
<div class="relative overflow-hidden rounded-xl bg-gradient-to-br from-teal-500 to-teal-700 p-4 shadow-sm">
<div class="absolute right-0 top-0 w-12 h-12 opacity-15">
<svg class="w-full h-full text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
</div>
<p class="text-[10px] font-semibold text-white/70 uppercase tracking-wider">Terakhir</p>
<p class="text-sm font-bold text-white mt-1">
@if($lastViolation) {{ $lastViolation->violation_date->diffForHumans() }} @else — @endif
</p>
</div>
</div>
@@ -144,70 +134,56 @@
{{-- ===== SP PROGRESS ===== --}}
@if($spThresholds->count() > 0)
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 p-6 mb-6 perspective-card">
<div class="flex items-center justify-between mb-5">
<h3 class="text-base font-semibold text-gray-900">Progress Ambang Surat Peringatan</h3>
<span class="text-xs text-gray-400">{{ $totalPoints }} / {{ $nextSpThreshold ? $nextSpThreshold->min_points : $spThresholds->last()->min_points }} poin</span>
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 p-6 mb-6">
<div class="flex items-center gap-3 mb-5">
<div class="w-8 h-8 rounded-lg bg-blue-50 flex items-center justify-center">
<i class="fa-solid fa-chart-line text-blue-500 text-sm"></i>
</div>
<div>
<h3 class="text-sm font-semibold text-gray-900">Progress Ambang SP</h3>
<p class="text-xs text-gray-400">{{ $totalPoints }} / {{ $nextSpThreshold ? $nextSpThreshold->min_points : $spThresholds->last()->min_points }} poin</p>
</div>
</div>
{{-- Progress Bar --}}
@php
$maxPoints = $nextSpThreshold ? $nextSpThreshold->min_points : $spThresholds->last()->min_points;
if ($totalPoints >= $spThresholds->last()->min_points) {
$progressPercent = 100;
} else {
$progressPercent = min(100, ($totalPoints / $maxPoints) * 100);
}
$progressPercent = $totalPoints >= $spThresholds->last()->min_points ? 100 : min(100, ($totalPoints / $maxPoints) * 100);
@endphp
<div class="relative h-3 bg-gray-100 rounded-full overflow-hidden mb-4">
<div class="sp-progress-bar h-full rounded-full transition-all duration-500 ease-out"
style="width: {{ $progressPercent }}%"></div>
<div class="sp-progress-bar h-full rounded-full transition-all duration-500" style="width: {{ $progressPercent }}%"></div>
</div>
{{-- Threshold Markers --}}
<div class="relative flex justify-between">
@foreach($spThresholds as $threshold)
@php
$tsReached = $totalPoints >= $threshold->min_points;
@endphp
@php $tsReached = $totalPoints >= $threshold->min_points; @endphp
<div class="flex flex-col items-center" style="flex:1">
<div class="flex items-center gap-1.5 mb-1.5">
<span class="w-3 h-3 rounded-full {{ $tsReached ? 'shadow-sm' : 'bg-gray-300' }}"
style="background-color: {{ $tsReached ? $threshold->color : '' }}"></span>
<span class="text-xs font-semibold {{ $tsReached ? '' : 'text-gray-400' }}"
style="color: {{ $tsReached ? $threshold->color : '' }}">
{{ $threshold->min_points }}
</span>
<span class="w-3 h-3 rounded-full {{ $tsReached ? 'shadow-sm' : 'bg-gray-300' }}" style="background-color: {{ $tsReached ? $threshold->color : '' }}"></span>
<span class="text-xs font-semibold {{ $tsReached ? '' : 'text-gray-400' }}" style="color: {{ $tsReached ? $threshold->color : '' }}">{{ $threshold->min_points }}</span>
</div>
<span class="text-[10px] {{ $tsReached ? 'font-medium' : 'text-gray-400' }} leading-tight text-center"
style="color: {{ $tsReached ? $threshold->color : '' }}">
{{ $threshold->name }}
</span>
<span class="text-[10px] {{ $tsReached ? 'font-medium' : 'text-gray-400' }}" style="color: {{ $tsReached ? $threshold->color : '' }}">{{ $threshold->name }}</span>
</div>
@endforeach
</div>
{{-- Status --}}
@if($currentSpLevel)
<div class="mt-5 p-3.5 rounded-xl text-sm flex items-start gap-3"
style="background-color: {{ $currentSpLevel->color }}10; border: 1px solid {{ $currentSpLevel->color }}20;">
<svg class="w-5 h-5 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"
style="color: {{ $currentSpLevel->color }}">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<div class="mt-5 p-4 rounded-xl flex items-start gap-3" style="background-color: {{ $currentSpLevel->color }}10; border: 1px solid {{ $currentSpLevel->color }}20;">
<div class="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0" style="background-color: {{ $currentSpLevel->color }}20;">
<i class="fa-solid fa-triangle-exclamation" style="color: {{ $currentSpLevel->color }}"></i>
</div>
<div>
<p class="font-semibold" style="color: {{ $currentSpLevel->color }}">{{ $currentSpLevel->name }} sudah tercapai</p>
<p class="text-gray-500 mt-0.5 text-xs">Siswa ini telah mencapai ambang minimal {{ $currentSpLevel->min_points }} poin. Surat peringatan telah diterbitkan.</p>
<p class="text-sm font-semibold" style="color: {{ $currentSpLevel->color }}">{{ $currentSpLevel->name }} Sudah Tercapai</p>
<p class="text-xs text-gray-500 mt-0.5">Siswa telah mencapai ambang {{ $currentSpLevel->min_points }} poin. Surat peringatan telah diterbitkan.</p>
</div>
</div>
@elseif($nextSpThreshold && $violationCount > 0)
<div class="mt-5 p-3.5 rounded-xl bg-blue-50 border border-blue-100 text-sm flex items-start gap-3">
<svg class="w-5 h-5 mt-0.5 text-blue-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<div class="mt-5 p-4 rounded-xl bg-blue-50 border border-blue-100 flex items-start gap-3">
<div class="w-8 h-8 rounded-lg bg-blue-100 flex items-center justify-center flex-shrink-0">
<i class="fa-solid fa-circle-info text-blue-600"></i>
</div>
<div>
<p class="font-semibold text-blue-700">Menuju {{ $nextSpThreshold->name }}</p>
<p class="text-blue-600/70 mt-0.5 text-xs">{{ $nextSpThreshold->min_points - $totalPoints }} poin lagi untuk mencapai ambang {{ $nextSpThreshold->name }} ({{ $nextSpThreshold->min_points }} poin)</p>
<p class="text-sm font-semibold text-blue-700">Menuju {{ $nextSpThreshold->name }}</p>
<p class="text-xs text-blue-500/70 mt-0.5">{{ $nextSpThreshold->min_points - $totalPoints }} poin lagi untuk mencapai {{ $nextSpThreshold->name }}</p>
</div>
</div>
@endif
@@ -217,76 +193,66 @@
{{-- ===== VIOLATIONS TIMELINE ===== --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between">
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-lg bg-blue-50 flex items-center justify-center">
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-orange-500 to-red-500 flex items-center justify-center shadow-sm">
<i class="fa-solid fa-triangle-exclamation text-white text-sm"></i>
</div>
<div>
<h3 class="text-base font-semibold text-gray-900">Riwayat Pelanggaran</h3>
<h3 class="text-sm font-semibold text-gray-900">Riwayat Pelanggaran</h3>
<p class="text-xs text-gray-400">{{ $violationCount }} catatan pelanggaran</p>
</div>
</div>
</div>
@forelse($student->violations as $v)
<div class="violation-timeline relative pl-14 pr-6 py-4 {{ !$loop->last ? 'border-b border-gray-50' : '' }} hover:bg-gray-50/50 transition">
{{-- Timeline dot --}}
<div class="absolute left-[13px] top-[18px] w-[15px] h-[15px] rounded-full border-2 flex items-center justify-center"
style="border-color: {{ $v->violationType?->category?->color ?? '#9ca3af' }}; background-color: white;">
<span class="w-[7px] h-[7px] rounded-full" style="background-color: {{ $v->violationType?->category?->color ?? '#9ca3af' }}"></span>
<div class="violation-timeline relative pl-[52px] pr-6 py-4 {{ !$loop->last ? 'border-b border-gray-50' : '' }} hover:bg-gray-50/50 transition">
<div class="absolute left-[15px] top-[15px] w-[16px] h-[16px] rounded-full border-[3px] bg-white flex items-center justify-center"
style="border-color: {{ $v->violationType?->category?->color ?? '#9ca3af' }}">
<span class="w-[6px] h-[6px] rounded-full" style="background-color: {{ $v->violationType?->category?->color ?? '#9ca3af' }}"></span>
</div>
<div class="flex items-start justify-between gap-4">
<div class="min-w-0 flex-1">
<div class="flex items-center gap-2 mb-1">
<span class="text-sm font-medium text-gray-900">{{ $v->violationType->name ?? '—' }}</span>
<span class="text-sm font-semibold text-gray-900">{{ $v->violationType->name ?? '—' }}</span>
@if($v->is_verified)
<span class="inline-flex items-center px-1.5 py-0.5 text-[10px] font-medium bg-blue-50 text-blue-600 border border-blue-200 rounded">✓ Terverifikasi</span>
<span class="inline-flex items-center gap-1 px-2 py-0.5 text-[10px] font-medium bg-emerald-50 text-emerald-600 rounded-full border border-emerald-200">
<span class="w-1 h-1 bg-emerald-500 rounded-full"></span>
Terverifikasi
</span>
@endif
</div>
@if($v->description)
<p class="text-sm text-gray-500 mt-0.5 line-clamp-2">{{ $v->description }}</p>
@endif
<div class="flex items-center gap-3 mt-1.5 text-xs text-gray-400">
<div class="flex flex-wrap items-center gap-x-3 gap-y-1 mt-2 text-xs text-gray-400">
<span class="inline-flex items-center gap-1">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
<i class="fa-solid fa-calendar text-gray-300"></i>
{{ $v->violation_date->format('d M Y') }}
@if($v->violation_time)
{{ $v->violation_time }}
@endif
@if($v->violation_time) {{ \Carbon\Carbon::parse($v->violation_time)->format('H:i') }} @endif
</span>
@if($v->location)
<span class="inline-flex items-center gap-1">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
<i class="fa-solid fa-location-dot text-gray-300"></i>
{{ $v->location }}
</span>
@endif
<span class="inline-flex items-center gap-1">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
</svg>
<i class="fa-solid fa-user-pen text-gray-300"></i>
{{ $v->recorder->name ?? '—' }}
</span>
</div>
{{-- Evidence photos --}}
@if($v->evidences && $v->evidences->count() > 0)
<div class="flex gap-2 mt-2">
<div class="flex gap-2 mt-2.5">
@foreach($v->evidences->take(3) as $ev)
<a href="{{ Storage::url($ev->file_path) }}" target="_blank"
class="w-14 h-14 rounded-lg border border-gray-200 overflow-hidden hover:ring-2 hover:ring-blue-300 transition-all">
class="w-12 h-12 rounded-lg border border-gray-200 overflow-hidden hover:ring-2 hover:ring-blue-300 transition-all">
<img src="{{ Storage::url($ev->file_path) }}" class="w-full h-full object-cover" alt="Bukti">
</a>
@endforeach
@if($v->evidences->count() > 3)
<div class="w-14 h-14 rounded-lg bg-gray-50 border border-gray-200 flex items-center justify-center text-xs text-gray-400 font-medium">
<div class="w-12 h-12 rounded-lg bg-gray-50 border border-gray-200 flex items-center justify-center text-[10px] text-gray-400 font-medium">
+{{ $v->evidences->count() - 3 }}
</div>
@endif
@@ -294,23 +260,26 @@
@endif
</div>
<div class="flex flex-col items-end flex-shrink-0">
<span class="text-sm font-bold {{ $v->points >= 50 ? 'text-blue-600' : ($v->points >= 15 ? 'text-blue-500' : 'text-blue-400') }}">
<div class="flex flex-col items-center flex-shrink-0 gap-2.5 min-w-[72px]">
<span class="inline-flex items-center px-3 py-1 text-xs font-bold rounded-lg shadow-sm
{{ $v->points >= 50 ? 'bg-red-500 text-white' : ($v->points >= 15 ? 'bg-yellow-500 text-white' : 'bg-blue-500 text-white') }}">
+{{ $v->points }}
</span>
@if($v->sanction)
<span class="text-[10px] text-gray-400 mt-0.5 line-clamp-1 max-w-[100px] text-right">{{ $v->sanction }}</span>
<span class="text-[10px] text-gray-400 leading-tight text-center line-clamp-2 max-w-[72px]">{{ $v->sanction }}</span>
@endif
<a href="{{ route('violations.show', $v->id) }}" class="text-[10px] text-blue-500 hover:text-blue-700 font-medium mt-1 transition">Detail</a>
<a href="{{ route('violations.show', $v->id) }}"
class="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 text-xs font-semibold text-blue-700 bg-blue-50 border border-blue-200 rounded-lg hover:bg-blue-100 hover:border-blue-300 transition-all shadow-sm w-full">
Detail
<i class="fa-solid fa-arrow-right text-[10px]"></i>
</a>
</div>
</div>
</div>
@empty
<div class="py-16 text-center">
<div class="w-16 h-16 rounded-full bg-blue-50 border border-blue-100 flex items-center justify-center mx-auto mb-4">
<svg class="w-7 h-7 text-blue-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<div class="w-16 h-16 rounded-full bg-gray-50 border border-gray-100 flex items-center justify-center mx-auto mb-4">
<i class="fa-solid fa-face-smile text-gray-300 text-2xl"></i>
</div>
<h4 class="text-sm font-medium text-gray-500 mb-1">Belum Ada Pelanggaran</h4>
<p class="text-xs text-gray-400">Siswa ini belum memiliki catatan pelanggaran</p>
@@ -322,14 +291,12 @@
@if($student->spLetters->count() > 0)
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between">
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-lg bg-blue-50 flex items-center justify-center">
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-violet-500 to-violet-600 flex items-center justify-center shadow-sm">
<i class="fa-solid fa-file-lines text-white text-sm"></i>
</div>
<div>
<h3 class="text-base font-semibold text-gray-900">Surat Peringatan</h3>
<h3 class="text-sm font-semibold text-gray-900">Surat Peringatan</h3>
<p class="text-xs text-gray-400">{{ $student->spLetters->count() }} surat diterbitkan</p>
</div>
</div>
@@ -340,108 +307,324 @@
<div class="flex items-center gap-3 min-w-0">
<div class="w-10 h-10 rounded-xl flex items-center justify-center flex-shrink-0"
style="background-color: {{ $sp->spThreshold?->color ?? '#3b82f6' }}15">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
style="color: {{ $sp->spThreshold?->color ?? '#3b82f6' }}">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
</svg>
<i class="fa-solid fa-file-lines" style="color: {{ $sp->spThreshold?->color ?? '#3b82f6' }}"></i>
</div>
<div class="min-w-0">
<p class="text-sm font-medium text-gray-900 truncate">
<p class="text-sm font-semibold text-gray-900 truncate">
{{ $sp->spThreshold?->name ?? 'SP' }}
@if($sp->status === 'draft')
<span class="inline-flex items-center px-1.5 py-0.5 text-[10px] font-medium bg-blue-50 text-blue-600 border border-blue-200 rounded ml-1.5">Draft</span>
<span class="inline-flex items-center px-2 py-0.5 text-[10px] font-medium bg-yellow-50 text-yellow-600 rounded-full border border-yellow-200 ml-1.5">Draft</span>
@elseif($sp->status === 'issued')
<span class="inline-flex items-center px-1.5 py-0.5 text-[10px] font-medium bg-emerald-50 text-emerald-600 border border-emerald-200 rounded ml-1.5">Terbit</span>
<span class="inline-flex items-center px-2 py-0.5 text-[10px] font-medium bg-emerald-50 text-emerald-600 rounded-full border border-emerald-200 ml-1.5">Terbit</span>
@endif
</p>
<p class="text-xs text-gray-400 mt-0.5">{{ $sp->letter_number }} • {{ $sp->created_at->format('d M Y') }}</p>
</div>
</div>
<div class="flex items-center gap-2 flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
<div class="flex items-center gap-2 flex-shrink-0">
<a href="{{ route('sp-letters.show', $sp->id) }}"
class="px-3 py-1.5 text-xs font-medium text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-gray-300 transition-all">
Lihat
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-gray-600 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition">
<i class="fa-solid fa-eye"></i>
<span class="hidden sm:inline">Lihat</span>
</a>
<a href="{{ route('sp-letters.print', $sp->id) }}"
class="px-3 py-1.5 text-xs font-medium text-blue-600 bg-blue-50 border border-blue-200 rounded-lg hover:bg-blue-100 transition-all">
Cetak
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-blue-600 bg-blue-50 border border-blue-200 rounded-lg hover:bg-blue-100 transition">
<i class="fa-solid fa-print"></i>
<span class="hidden sm:inline">Cetak</span>
</a>
</div>
{{-- Always show on mobile --}}
<div class="flex items-center gap-2 flex-shrink-0 sm:hidden">
<a href="{{ route('sp-letters.show', $sp->id) }}" class="text-xs text-blue-600 font-medium">Lihat</a>
</div>
</div>
@endforeach
</div>
</div>
@endif
{{-- ===== STUDENT INFO DETAILS ===== --}}
{{-- ===== INFO DETAILS ===== --}}
<div x-data="{ showEditModal: false }">
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100">
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-lg bg-blue-50 flex items-center justify-center">
<svg class="w-4 h-4 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between gap-4">
<div class="flex items-center gap-3 min-w-0">
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center shadow-sm flex-shrink-0">
<i class="fa-solid fa-circle-info text-white text-sm"></i>
</div>
<div class="min-w-0">
<h3 class="text-sm font-semibold text-gray-900 truncate">Informasi Lengkap</h3>
<p class="text-xs text-gray-400 truncate">Data diri siswa</p>
</div>
<h3 class="text-base font-semibold text-gray-900">Informasi Lengkap</h3>
</div>
<button @click="showEditModal = true"
class="inline-flex items-center gap-1.5 px-3.5 py-2 text-xs font-semibold text-blue-600 bg-blue-50 border border-blue-200 rounded-lg hover:bg-blue-100 transition flex-shrink-0 shadow-sm">
<i class="fa-solid fa-pen-to-square"></i>
Update Data
</button>
</div>
<div class="p-6">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
<div>
<p class="text-[10px] font-semibold text-gray-400 uppercase tracking-widest mb-1.5">NISN</p>
<p class="text-sm font-medium text-gray-900 font-mono">{{ $student->nisn ?? '—' }}</p>
@php
// Identitas: kiri
$identityLeft = [
['NISN', $student->nisn, 'fa-id-card'],
['NIS', $student->student_number, 'fa-hashtag'],
];
// Identitas: kanan
$identityRight = [
['Jenis Kelamin', $student->gender === 'L' ? 'Laki-laki' : ($student->gender === 'P' ? 'Perempuan' : '—'), 'fa-venus-mars'],
['Tempat Lahir', $student->place_of_birth, 'fa-map-pin'],
['Tanggal Lahir', $student->date_of_birth ? $student->date_of_birth->format('d M Y') : '—', 'fa-cake-candles'],
];
// Akademik
$academic = [
['Kelas', $student->class_name, 'fa-building'],
['Jurusan', $student->department_name ?? $student->department_code, 'fa-graduation-cap'],
['Tahun Akademik', $student->academic_year_name, 'fa-calendar'],
];
// Kontak
$contact = [
['Telepon', $student->phone_number, 'fa-phone'],
['Email', $student->email, 'fa-envelope'],
];
// Sistem
$system = [
['Terakhir Sinkron', $student->synced_at ? $student->synced_at->format('d M Y H:i') : '—', 'fa-cloud-arrow-down'],
];
@endphp
<div class="space-y-5">
{{-- Row: Identitas --}}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-5">
{{-- Identitas Kiri --}}
<div class="space-y-3">
<div class="flex items-center gap-2">
<div class="w-6 h-6 rounded-md bg-blue-100 flex items-center justify-center">
<i class="fa-solid fa-id-card text-blue-600 text-[10px]"></i>
</div>
<span class="text-[11px] font-bold text-blue-600 uppercase tracking-wider">Identitas</span>
</div>
<div class="space-y-2">
@foreach($identityLeft as $f)
@if($f[1])
<div class="flex items-center justify-between bg-gray-50 rounded-xl border border-gray-100 px-4 py-3">
<span class="text-xs text-gray-400">{{ $f[0] }}</span>
<span class="text-sm font-semibold text-gray-900 {{ $f[0] === 'NISN' || $f[0] === 'NIS' ? 'font-mono' : '' }}">{{ $f[1] }}</span>
</div>
@endif
@endforeach
</div>
</div>
{{-- Identitas Kanan --}}
<div class="space-y-3">
<div class="flex items-center gap-2">
<div class="w-6 h-6 rounded-md bg-blue-100 flex items-center justify-center">
<i class="fa-solid fa-user text-blue-600 text-[10px]"></i>
</div>
<span class="text-[11px] font-bold text-blue-600 uppercase tracking-wider">Pribadi</span>
</div>
<div class="space-y-2">
@foreach($identityRight as $f)
@if($f[1])
<div class="flex items-center justify-between bg-gray-50 rounded-xl border border-gray-100 px-4 py-3">
<span class="text-xs text-gray-400">{{ $f[0] }}</span>
<span class="text-sm font-semibold text-gray-900 max-w-[55%] text-right truncate">{{ $f[1] }}</span>
</div>
@endif
@endforeach
</div>
</div>
</div>
<div>
<p class="text-[10px] font-semibold text-gray-400 uppercase tracking-widest mb-1.5">NIS</p>
<p class="text-sm font-medium text-gray-900 font-mono">{{ $student->student_number ?? '—' }}</p>
</div>
<div>
<p class="text-[10px] font-semibold text-gray-400 uppercase tracking-widest mb-1.5">Jenis Kelamin</p>
<p class="text-sm font-medium text-gray-900">{{ $student->gender === 'L' ? 'Laki-laki' : ($student->gender === 'P' ? 'Perempuan' : '—') }}</p>
</div>
<div>
<p class="text-[10px] font-semibold text-gray-400 uppercase tracking-widest mb-1.5">Tempat, Tanggal Lahir</p>
<p class="text-sm font-medium text-gray-900">
{{ $student->place_of_birth ?? '—' }}{{ $student->place_of_birth && $student->date_of_birth ? ', ' : '' }}
{{ $student->date_of_birth ? $student->date_of_birth->format('d M Y') : '—' }}
</p>
</div>
<div>
<p class="text-[10px] font-semibold text-gray-400 uppercase tracking-widest mb-1.5">Kelas</p>
<p class="text-sm font-medium text-gray-900">{{ $student->class_name ?? '—' }}</p>
</div>
<div>
<p class="text-[10px] font-semibold text-gray-400 uppercase tracking-widest mb-1.5">Jurusan</p>
<p class="text-sm font-medium text-gray-900">{{ $student->department_name ?? $student->department_code ?? '—' }}</p>
</div>
<div class="sm:col-span-2 lg:col-span-3">
<p class="text-[10px] font-semibold text-gray-400 uppercase tracking-widest mb-1.5">Alamat</p>
<p class="text-sm text-gray-700">{{ $student->address ?? '—' }}</p>
</div>
@if($student->phone_number || $student->email)
<div>
<p class="text-[10px] font-semibold text-gray-400 uppercase tracking-widest mb-1.5">Telepon</p>
<p class="text-sm font-medium text-gray-900">{{ $student->phone_number ?? '—' }}</p>
</div>
<div>
<p class="text-[10px] font-semibold text-gray-400 uppercase tracking-widest mb-1.5">Email</p>
<p class="text-sm font-medium text-gray-900">{{ $student->email ?? '—' }}</p>
{{-- Row: Akademik --}}
@php $academicHasValue = collect($academic)->filter(fn($f) => $f[1] !== '—' && $f[1] !== null)->count() > 0; @endphp
@if($academicHasValue)
<div class="space-y-3">
<div class="flex items-center gap-2">
<div class="w-6 h-6 rounded-md bg-violet-100 flex items-center justify-center">
<i class="fa-solid fa-graduation-cap text-violet-600 text-[10px]"></i>
</div>
<span class="text-[11px] font-bold text-violet-600 uppercase tracking-wider">Akademik</span>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-2">
@foreach($academic as $f)
@if($f[1])
<div class="bg-gray-50 rounded-xl border border-gray-100 px-4 py-3">
<p class="text-xs text-gray-400">{{ $f[0] }}</p>
<p class="text-sm font-semibold text-gray-900 mt-0.5">{{ $f[1] }}</p>
</div>
@endif
@endforeach
</div>
</div>
@endif
<div>
<p class="text-[10px] font-semibold text-gray-400 uppercase tracking-widest mb-1.5">Tahun Akademik</p>
<p class="text-sm font-medium text-gray-900">{{ $student->academic_year_name ?? '—' }}</p>
{{-- Row: Kontak --}}
<div class="space-y-3">
<div class="flex items-center gap-2">
<div class="w-6 h-6 rounded-md bg-emerald-100 flex items-center justify-center">
<i class="fa-solid fa-address-book text-emerald-600 text-[10px]"></i>
</div>
<span class="text-[11px] font-bold text-emerald-600 uppercase tracking-wider">Kontak</span>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
@foreach($contact as $f)
<div class="bg-gray-50 rounded-xl border border-gray-100 px-4 py-3">
<p class="text-xs text-gray-400">{{ $f[0] }}</p>
<p class="text-sm font-medium text-gray-900 mt-0.5 font-mono">{{ $f[1] ?: '—' }}</p>
</div>
@endforeach
</div>
</div>
<div>
<p class="text-[10px] font-semibold text-gray-400 uppercase tracking-widest mb-1.5">Terakhir Sinkron</p>
<p class="text-sm font-medium text-gray-900">{{ $student->synced_at ? $student->synced_at->format('d M Y H:i') : '—' }}</p>
{{-- Alamat --}}
<div class="space-y-3">
<div class="flex items-center gap-2">
<div class="w-6 h-6 rounded-md bg-blue-100 flex items-center justify-center">
<i class="fa-solid fa-location-dot text-blue-600 text-[10px]"></i>
</div>
<span class="text-[11px] font-bold text-blue-600 uppercase tracking-wider">Alamat</span>
</div>
<div class="bg-gray-50 rounded-xl border border-gray-100 px-4 py-3">
<p class="text-sm text-gray-700 leading-relaxed">{{ $student->address ?: '—' }}</p>
</div>
</div>
{{-- Row: Sistem --}}
<div class="space-y-3">
<div class="flex items-center gap-2">
<div class="w-6 h-6 rounded-md bg-gray-200 flex items-center justify-center">
<i class="fa-solid fa-gear text-gray-500 text-[10px]"></i>
</div>
<span class="text-[11px] font-bold text-gray-500 uppercase tracking-wider">Sistem</span>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
@foreach($system as $f)
@if($f[1])
<div class="bg-gray-50 rounded-xl border border-gray-100 px-4 py-3">
<p class="text-xs text-gray-400">{{ $f[0] }}</p>
<p class="text-sm font-medium text-gray-900 mt-0.5">{{ $f[1] }}</p>
</div>
@endif
@endforeach
</div>
</div>
</div>
</div>
</div>
{{-- Edit Modal --}}
<div x-show="showEditModal"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 z-50 overflow-y-auto" style="display: none;">
<div class="flex items-center justify-center min-h-screen px-4 py-4">
<div class="fixed inset-0 bg-gray-900/40 backdrop-blur-sm"></div>
<div class="relative bg-white rounded-2xl shadow-xl border border-gray-200 w-full max-w-2xl mx-4 max-h-[90vh] overflow-y-auto">
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between sticky top-0 bg-white z-10">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center shadow-sm">
<i class="fa-solid fa-pen-to-square text-white text-sm"></i>
</div>
<div>
<h3 class="text-sm font-semibold text-gray-900">Update Data Siswa</h3>
<p class="text-xs text-gray-400">Perbarui data diri {{ $student->full_name }}</p>
</div>
</div>
<button @click="showEditModal = false" class="w-8 h-8 rounded-lg flex items-center justify-center text-gray-400 hover:text-gray-600 hover:bg-gray-100 transition">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
<form action="{{ route('students.update', $student->id) }}" method="POST" class="p-6">
@csrf @method('PUT')
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">Nama Lengkap <span class="text-red-500">*</span></label>
<input type="text" name="full_name" value="{{ $student->full_name }}" required
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">NISN</label>
<input type="text" name="nisn" value="{{ $student->nisn }}"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">NIS</label>
<input type="text" name="student_number" value="{{ $student->student_number }}"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Jenis Kelamin</label>
<select name="gender"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
<option value="">—</option>
<option value="L" @selected($student->gender === 'L')>Laki-laki</option>
<option value="P" @selected($student->gender === 'P')>Perempuan</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Tempat Lahir</label>
<input type="text" name="place_of_birth" value="{{ $student->place_of_birth }}"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Tanggal Lahir</label>
<input type="date" name="date_of_birth" value="{{ $student->date_of_birth ? $student->date_of_birth->format('Y-m-d') : '' }}"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
<div class="sm:col-span-2">
<label class="block text-sm font-medium text-gray-700 mb-1">Alamat</label>
<textarea name="address" rows="2"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">{{ $student->address }}</textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">No. Telepon</label>
<input type="text" name="phone_number" value="{{ $student->phone_number }}"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
<input type="email" name="email" value="{{ $student->email }}"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Kelas</label>
<input type="text" name="class_name" value="{{ $student->class_name }}"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Tingkat Kelas</label>
<input type="text" name="class_level" value="{{ $student->class_level }}"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Kode Jurusan</label>
<input type="text" name="department_code" value="{{ $student->department_code }}"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Nama Jurusan</label>
<input type="text" name="department_name" value="{{ $student->department_name }}"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
</div>
<div class="flex items-center justify-end gap-3 mt-6 pt-4 border-t border-gray-100">
<button type="button" @click="showEditModal = false"
class="px-5 py-2.5 text-sm font-medium text-gray-600 bg-white border border-gray-200 rounded-xl hover:bg-gray-50 transition">
Batal
</button>
<button type="submit"
class="px-5 py-2.5 text-sm font-semibold text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm inline-flex items-center gap-2">
<i class="fa-solid fa-floppy-disk text-xs"></i>
Simpan Perubahan
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
+3 -3
View File
@@ -115,7 +115,7 @@
</button>
@if($user->id !== auth()->id())
<form action="{{ route('users.destroy', $user->id) }}" method="POST" class="inline"
x-data x-on:submit.prevent="if(await window.confirmSwal({text:'Hapus user ini?'})) $el.submit()"">
x-data x-on:submit.prevent="if(await window.confirmSwal({text:'Hapus user ini?'})) $el.submit()">
@csrf @method('DELETE')
<button type="submit"
class="p-2 text-red-500 hover:bg-red-50 rounded-lg transition">
@@ -151,8 +151,8 @@
{{-- Modal --}}
<div x-show="modalOpen" class="fixed inset-0 z-50 overflow-y-auto" style="display: none;">
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:p-0">
<div @click="modalOpen = false" class="fixed inset-0 bg-gray-500 bg-opacity-50 transition-opacity"></div>
<div @click.away="modalOpen = false"
<div class="fixed inset-0 bg-gray-500 bg-opacity-50 transition-opacity"></div>
<div
class="relative inline-block align-bottom bg-white rounded-2xl shadow-xl border border-gray-200 text-left overflow-hidden transform transition-all sm:align-middle sm:max-w-lg sm:w-full">
<div class="px-6 py-5 border-b border-gray-100 flex items-center justify-between">
<div class="flex items-center space-x-3">
+215 -97
View File
@@ -3,108 +3,226 @@
@section('title', 'Data Pelanggaran')
@section('content')
<div class="mb-6 flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold text-gray-900">Data Pelanggaran</h1>
<p class="text-sm text-gray-500">Riwayat pelanggaran siswa</p>
<div>
{{-- Header --}}
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 class="text-2xl font-bold text-gray-900 tracking-tight">Data Pelanggaran</h1>
<p class="text-sm text-gray-500 mt-1">Riwayat pelanggaran siswa</p>
</div>
<a href="{{ route('violations.create') }}"
class="inline-flex items-center gap-2 px-4 py-2.5 text-sm font-semibold text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm">
<i class="fa-solid fa-plus text-xs"></i>
Input Pelanggaran
</a>
</div>
<a href="{{ route('violations.create') }}" class="px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition flex items-center space-x-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
<span>Input Pelanggaran</span>
</a>
</div>
{{-- Filter --}}
<form method="GET" class="bg-white p-4 rounded-xl shadow-sm border border-gray-200 mb-6">
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div>
<input type="text" name="search" placeholder="Cari NISN/Nama..." value="{{ request('search') }}"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<select name="category_id" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
<option value="">Semua Kategori</option>
@foreach($categories as $cat)
<option value="{{ $cat->id }}" @selected(request('category_id') == $cat->id)>{{ $cat->name }}</option>
@endforeach
</select>
</div>
<div>
<input type="date" name="date_from" value="{{ request('date_from') }}" placeholder="Dari tanggal"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
</div>
<div class="flex space-x-2">
<input type="date" name="date_to" value="{{ request('date_to') }}" placeholder="Sampai tanggal"
class="flex-1 px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500">
<button type="submit" class="px-3 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700">Filter</button>
@if(request()->anyFilled(['search','category_id','date_from','date_to']))
<a href="{{ route('violations.index') }}" class="px-3 py-2 border border-gray-300 text-gray-600 text-sm rounded-lg hover:bg-gray-50">Reset</a>
@endif
</div>
{{-- Filter Card --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 mb-6">
<form method="GET">
<div class="p-5 space-y-4">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-3">
{{-- Search --}}
<div class="sm:col-span-2 lg:col-span-1 relative">
<i class="fa-solid fa-magnifying-glass absolute left-3.5 top-1/2 -translate-y-1/2 text-gray-400 text-sm pointer-events-none"></i>
<input type="text" name="search" placeholder="Cari NISN/Nama..." value="{{ request('search') }}"
class="w-full pl-9 pr-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
{{-- Kategori --}}
<div>
<select name="handling_status"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
<option value="">Semua Status</option>
<option value="unhandled" @selected(request('handling_status') === 'unhandled')>Belum Ditangani</option>
<option value="in_progress" @selected(request('handling_status') === 'in_progress')>Dalam Proses</option>
<option value="resolved" @selected(request('handling_status') === 'resolved')>Selesai</option>
</select>
</div>
{{-- Kategori --}}
<div>
<select name="category_id"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
<option value="">Semua Kategori</option>
@foreach($categories as $cat)
<option value="{{ $cat->id }}" @selected(request('category_id') == $cat->id)>{{ $cat->name }}</option>
@endforeach
</select>
</div>
{{-- Tanggal Dari --}}
<div>
<input type="date" name="date_from" value="{{ request('date_from') }}" placeholder="Dari tanggal"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
{{-- Tanggal Sampai --}}
<div>
<input type="date" name="date_to" value="{{ request('date_to') }}" placeholder="Sampai tanggal"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
{{-- Actions --}}
<div class="flex items-center gap-2">
<button type="submit"
class="flex-1 px-4 py-2.5 text-sm font-medium text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm">
<i class="fa-solid fa-filter mr-1.5"></i>Filter
</button>
@if(request()->anyFilled(['search','handling_status','category_id','date_from','date_to']))
<a href="{{ route('violations.index') }}"
class="px-4 py-2.5 text-sm font-medium text-gray-600 bg-white border border-gray-200 rounded-xl hover:bg-gray-50 transition">
<i class="fa-solid fa-xmark"></i>
</a>
@endif
</div>
</div>
</div>
</form>
</div>
</form>
{{-- Table --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Tanggal</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Siswa</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Pelanggaran</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">Poin</th>
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase">Verifikasi</th>
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Oleh</th>
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">Aksi</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
@forelse($violations as $v)
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-700">{{ $v->violation_date->format('d/m/Y') }}</td>
<td class="px-4 py-3">
<div class="text-sm font-medium text-gray-900">{{ $v->student->full_name ?? '-' }}</div>
<div class="text-xs text-gray-500">{{ $v->student->nisn ?? '' }} - {{ $v->student->class_name ?? '' }}</div>
</td>
<td class="px-4 py-3">
<div class="flex items-center space-x-2">
<span class="w-2.5 h-2.5 rounded-full flex-shrink-0" style="background-color: {{ $v->violationType?->category?->color ?? '#6b7280' }}"></span>
<span class="text-sm text-gray-700">{{ $v->violationType->name ?? '-' }}</span>
</div>
</td>
<td class="px-4 py-3 text-center">
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
+{{ $v->points }}
</span>
</td>
<td class="px-4 py-3 text-center">
@if($v->is_verified)
<span class="text-xs text-green-600 font-medium">✓ Terverifikasi</span>
@else
<span class="text-xs text-yellow-600 font-medium">Belum</span>
@endif
</td>
<td class="px-4 py-3 text-sm text-gray-500">{{ $v->recorder->name ?? '-' }}</td>
<td class="px-4 py-3 text-right whitespace-nowrap">
<a href="{{ route('violations.show', $v->id) }}" class="text-blue-600 hover:text-blue-800 text-sm font-medium">Detail</a>
</td>
{{-- Table --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-100">
<thead>
<tr class="bg-gray-50/80">
<th class="px-5 py-3.5 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider">Tanggal</th>
<th class="px-5 py-3.5 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider">Siswa</th>
<th class="px-5 py-3.5 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider">Pelanggaran</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-gray-400 uppercase tracking-wider">Poin</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-gray-400 uppercase tracking-wider">Verifikasi</th>
<th class="px-5 py-3.5 text-center text-xs font-semibold text-gray-400 uppercase tracking-wider hidden lg:table-cell">Penanganan</th>
<th class="px-5 py-3.5 text-left hidden lg:table-cell text-xs font-semibold text-gray-400 uppercase tracking-wider">Oleh</th>
<th class="px-5 py-3.5 text-right text-xs font-semibold text-gray-400 uppercase tracking-wider">Aksi</th>
</tr>
@empty
<tr>
<td colspan="7" class="px-4 py-12 text-center text-gray-500">
<svg class="mx-auto h-12 w-12 text-gray-300 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"></path></svg>
<p>Belum ada pelanggaran tercatat</p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($violations->hasPages())
<div class="px-4 py-3 border-t border-gray-100">
{{ $violations->links() }}
</thead>
<tbody class="divide-y divide-gray-50">
@forelse($violations as $v)
<tr class="hover:bg-gray-50/50 transition">
{{-- Tanggal --}}
<td class="px-5 py-4 whitespace-nowrap">
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-lg bg-blue-50 flex items-center justify-center flex-shrink-0">
<i class="fa-solid fa-calendar text-blue-500 text-xs"></i>
</div>
<div>
<p class="text-sm font-medium text-gray-900">{{ $v->violation_date->format('d/m/Y') }}</p>
@if($v->violation_time)
<p class="text-xs text-gray-400">{{ \Carbon\Carbon::parse($v->violation_time)->format('H:i') }}</p>
@endif
</div>
</div>
</td>
{{-- Siswa --}}
<td class="px-5 py-4">
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center flex-shrink-0">
<span class="text-xs font-bold text-gray-500">{{ strtoupper(substr($v->student->full_name ?? '?', 0, 1)) }}</span>
</div>
<div class="min-w-0">
<p class="text-sm font-semibold text-gray-900 truncate max-w-[180px]">{{ $v->student->full_name ?? '-' }}</p>
<div class="flex flex-wrap items-center gap-1.5 mt-0.5">
<span class="inline-flex items-center px-2 py-0.5 text-[10px] font-mono font-medium bg-gray-100 text-gray-600 rounded-full border border-gray-200">{{ $v->student->nisn ?? '—' }}</span>
<span class="inline-flex items-center px-2 py-0.5 text-[10px] font-medium bg-blue-50 text-blue-600 rounded-full border border-blue-200">{{ $v->student->class_name ?? '—' }}</span>
</div>
</div>
</div>
</td>
{{-- Pelanggaran --}}
<td class="px-5 py-4">
<div class="flex items-center gap-2 min-w-0">
<span class="w-2.5 h-2.5 rounded-full flex-shrink-0" style="background-color: {{ $v->violationType?->category?->color ?? '#6b7280' }}"></span>
<span class="text-sm text-gray-700 truncate max-w-[180px]">{{ $v->violationType->name ?? '-' }}</span>
</div>
</td>
{{-- Poin --}}
<td class="px-5 py-4 text-center whitespace-nowrap">
<span class="inline-flex items-center px-2.5 py-1 text-xs font-bold rounded-lg
{{ $v->points >= 50 ? 'bg-red-50 text-red-700' : ($v->points >= 15 ? 'bg-yellow-50 text-yellow-700' : 'bg-blue-50 text-blue-700') }}">
+{{ $v->points }}
</span>
</td>
{{-- Verifikasi --}}
<td class="px-5 py-4 text-center whitespace-nowrap">
@if($v->is_verified)
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium bg-green-50 text-green-700 border border-green-200 rounded-full">
<span class="w-1.5 h-1.5 bg-green-500 rounded-full"></span>
Terverifikasi
</span>
@else
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium bg-yellow-50 text-yellow-700 border border-yellow-200 rounded-full">
<span class="w-1.5 h-1.5 bg-yellow-500 rounded-full"></span>
Belum
</span>
@endif
</td>
{{-- Penanganan --}}
<td class="px-5 py-4 text-center whitespace-nowrap hidden lg:table-cell">
@php
$statusStyles = [
'unhandled' => 'bg-red-50 text-red-700 border-red-200',
'in_progress' => 'bg-yellow-50 text-yellow-700 border-yellow-200',
'resolved' => 'bg-green-50 text-green-700 border-green-200',
];
$statusLabels = [
'unhandled' => 'Belum Ditangani',
'in_progress' => 'Dalam Proses',
'resolved' => 'Selesai',
];
$statusDots = [
'unhandled' => 'bg-red-500',
'in_progress' => 'bg-yellow-500',
'resolved' => 'bg-green-500',
];
@endphp
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium border rounded-full {{ $statusStyles[$v->handling_status] ?? 'bg-gray-50 text-gray-700' }}">
<span class="w-1.5 h-1.5 rounded-full {{ $statusDots[$v->handling_status] ?? 'bg-gray-500' }}"></span>
{{ $statusLabels[$v->handling_status] ?? ucfirst($v->handling_status) }}
</span>
</td>
{{-- Oleh --}}
<td class="px-5 py-4 hidden lg:table-cell">
<div class="flex items-center gap-2">
<div class="w-6 h-6 rounded-full bg-gray-100 flex items-center justify-center flex-shrink-0">
<span class="text-[9px] font-bold text-gray-500">{{ strtoupper(substr($v->recorder->name ?? '?', 0, 1)) }}</span>
</div>
<span class="text-sm text-gray-500 truncate max-w-[120px]">{{ $v->recorder->name ?? '-' }}</span>
</div>
</td>
{{-- Aksi --}}
<td class="px-5 py-4 text-right whitespace-nowrap">
<a href="{{ route('violations.show', $v->id) }}"
class="inline-flex items-center gap-1.5 px-3.5 py-1.5 text-xs font-medium text-blue-600 bg-blue-50 border border-blue-200 rounded-lg hover:bg-blue-100 transition">
<i class="fa-solid fa-eye"></i>
Detail
</a>
</td>
</tr>
@empty
<tr>
<td colspan="8" class="px-5 py-20 text-center">
<div class="flex flex-col items-center">
<div class="w-14 h-14 rounded-full bg-gray-50 border border-gray-100 flex items-center justify-center mb-4">
<i class="fa-solid fa-triangle-exclamation text-gray-300 text-xl"></i>
</div>
<p class="text-sm font-medium text-gray-500 mb-1">Belum ada pelanggaran</p>
<p class="text-xs text-gray-400">Data pelanggaran akan muncul di sini setelah dicatat</p>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@endif
{{-- Pagination --}}
@if($violations->hasPages())
<div class="px-5 py-3 border-t border-gray-100 bg-gray-50/50">
{{ $violations->appends(request()->query())->links() }}
</div>
@endif
{{-- Summary --}}
<div class="px-5 py-3 border-t border-gray-100 bg-gray-50/50 flex items-center justify-between text-xs text-gray-400">
<span>Menampilkan {{ $violations->firstItem() ?? 0 }}–{{ $violations->lastItem() ?? 0 }} dari {{ $violations->total() }} pelanggaran</span>
<span class="font-medium">{{ $violations->total() }} total</span>
</div>
</div>
</div>
@endsection
+587 -131
View File
@@ -2,135 +2,252 @@
@section('title', 'Detail Pelanggaran')
@push('styles')
<style>
.evidence-grid img {
transition: transform 0.3s ease;
}
.evidence-grid a:hover img {
transform: scale(1.05);
}
.evidence-grid a:hover .evidence-overlay {
opacity: 1;
}
.detail-row:last-of-type {
border-bottom: none !important;
}
.status-dot {
position: absolute;
left: 0;
top: 4px;
}
</style>
@endpush
@section('content')
<div>
{{-- Header --}}
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
<div>
<div class="flex items-center space-x-3">
<a href="{{ route('violations.index') }}" class="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition">
<i class="fa-solid fa-arrow-left"></i>
</a>
<div>
<h1 class="text-2xl font-bold text-gray-900">Detail Pelanggaran</h1>
<p class="text-sm text-gray-500 mt-0.5">Informasi lengkap pelanggaran siswa</p>
</div>
</div>
<nav class="flex items-center gap-1.5 text-sm text-gray-400 mb-1">
<a href="{{ route('violations.index') }}" class="hover:text-gray-600 transition">Data Pelanggaran</a>
<span class="text-gray-300">/</span>
<span class="text-gray-700 font-medium truncate max-w-[200px]">Detail</span>
</nav>
<h1 class="text-xl sm:text-2xl font-bold text-gray-900 tracking-tight">Detail Pelanggaran</h1>
</div>
<div class="flex items-center space-x-2">
<div class="flex flex-wrap items-center gap-2">
@if(!$violation->is_verified)
<form action="{{ route('violations.verify', $violation->id) }}" method="POST">
@csrf
<button type="submit" class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-green-600 rounded-xl hover:bg-green-700 transition shadow-sm">
<i class="fa-solid fa-check-circle mr-1.5"></i>
Verifikasi
<button type="submit"
class="inline-flex items-center gap-1.5 px-4 py-2 text-sm font-semibold text-white bg-green-600 rounded-xl hover:bg-green-700 transition shadow-sm">
<i class="fa-solid fa-check-circle text-xs"></i>
<span>Verifikasi</span>
</button>
</form>
@endif
<form action="{{ route('violations.destroy', $violation->id) }}" method="POST" x-data onsubmit="event.preventDefault(); Swal.fire({title: 'Konfirmasi', text: 'Hapus pelanggaran ini?', icon: 'warning', showCancelButton: true, confirmButtonColor: '#dc2626', cancelButtonColor: '#6b7280', confirmButtonText: 'Ya, hapus!', cancelButtonText: 'Batal'}).then(r => { if(r.isConfirmed) { .target.submit(); } })">
<form action="{{ route('violations.destroy', $violation->id) }}" method="POST" x-data x-on:submit.prevent="if(await window.confirmSwal({text:'Hapus pelanggaran ini?'})) $el.submit()">
@csrf @method('DELETE')
<button type="submit" class="inline-flex items-center px-4 py-2 text-sm font-medium text-blue-600 bg-white border border-blue-200 rounded-xl hover:bg-blue-50 transition">
<i class="fa-solid fa-trash-can mr-1.5"></i>
Hapus
<button type="submit"
class="inline-flex items-center gap-1.5 px-3.5 py-2 text-sm font-medium text-red-600 bg-white border border-red-200 rounded-xl hover:bg-red-50 transition shadow-sm">
<i class="fa-solid fa-trash-can text-xs"></i>
<span class="hidden sm:inline">Hapus</span>
</button>
</form>
</div>
</div>
{{-- Content Grid --}}
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
{{-- Left: Info Siswa + Pelanggaran --}}
{{-- ===== LEFT COLUMN ===== --}}
<div class="lg:col-span-2 space-y-6">
{{-- Card Siswa --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div class="p-5 flex items-center space-x-4">
<div class="w-14 h-14 rounded-full flex items-center justify-center text-lg font-bold flex-shrink-0"
style="background-color: {{ $violation->violationType?->category?->color ?? '#6b7280' }}20; color: {{ $violation->violationType?->category?->color ?? '#6b7280' }}">
{{ strtoupper(substr($violation->student->full_name ?? '?', 0, 1)) }}
{{-- Student Profile Card --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="relative">
{{-- Gradient strip di bagian atas card --}}
<div class="bg-gradient-to-r from-blue-500 via-blue-400 to-sky-300 h-16 sm:h-20"></div>
{{-- Nama & badge --}}
<div class="px-6 sm:px-8 pt-[22px] sm:pt-[26px]">
<div class="inline-flex items-center px-4 py-2 sm:px-5 sm:py-2.5 bg-white shadow-sm border border-gray-200 rounded-xl max-w-full">
<h2 class="text-base sm:text-lg font-bold text-gray-900 truncate">{{ $violation->student->full_name ?? '-' }}</h2>
</div>
<div class="flex flex-wrap items-center gap-2 mt-2.5">
<span class="inline-flex items-center gap-1.5 px-3 py-1 text-xs font-mono font-medium bg-gray-100 text-gray-600 rounded-lg border border-gray-200">{{ $violation->student->nisn ?? '—' }}</span>
<span class="inline-flex items-center gap-1.5 px-3 py-1 text-xs font-medium bg-emerald-50 text-emerald-600 rounded-lg border border-emerald-200">{{ $violation->student->class_name ?? '—' }}</span>
@if($violation->student->department_name)
<span class="inline-flex items-center gap-1.5 px-3 py-1 text-xs font-medium bg-purple-50 text-purple-600 rounded-lg border border-purple-200">{{ $violation->student->department_name }}</span>
@endif
</div>
</div>
<div class="flex-1 min-w-0">
<h2 class="text-lg font-bold text-gray-900 truncate">{{ $violation->student->full_name ?? '-' }}</h2>
<p class="text-sm text-gray-500">{{ $violation->student->nisn ?? '' }} <span class="mx-1.5">•</span> {{ $violation->student->class_name ?? '' }}</p>
<p class="text-xs text-gray-400">{{ $violation->student->department_name ?? '' }}</p>
</div>
{{-- Stat row — dengan jarak dari avatar --}}
<div class="px-6 sm:px-8 pt-6 sm:pt-8 pb-5 sm:pb-6">
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
{{-- Total Poin --}}
<div class="relative overflow-hidden rounded-xl bg-gradient-to-br from-orange-500 to-red-600 p-4 shadow-sm">
<div class="absolute right-0 top-0 w-16 h-16 opacity-20">
<svg class="w-full h-full text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
</div>
<p class="text-[10px] font-semibold text-white/70 uppercase tracking-wider">Total Poin</p>
<p class="text-3xl font-bold text-white mt-1">{{ $violation->student->total_points }}</p>
<p class="text-[10px] text-white/50 mt-0.5">akumulasi</p>
</div>
<div class="flex-shrink-0 text-right">
<p class="text-xs text-gray-500">Total Poin</p>
<p class="text-2xl font-bold" style="color: {{ $violation->violationType?->category?->color ?? '#6b7280' }}">{{ $violation->student->total_points }}</p>
{{-- Poin Pelanggaran --}}
<div class="relative overflow-hidden rounded-xl p-4 shadow-sm"
style="background: linear-gradient(135deg, {{ $violation->points >= 50 ? '#dc2626' : ($violation->points >= 15 ? '#d97706' : '#2563eb') }}, {{ $violation->points >= 50 ? '#b91c1c' : ($violation->points >= 15 ? '#b45309' : '#1d4ed8') }});">
<div class="absolute right-0 top-0 w-16 h-16 opacity-10">
<svg class="w-full h-full text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"/></svg>
</div>
<p class="text-[10px] font-semibold text-white/70 uppercase tracking-wider">Poin Pelanggaran</p>
<p class="text-2xl font-bold text-white mt-1">+{{ $violation->points }}</p>
<p class="text-[10px] text-white/50 mt-0.5">pelanggaran ini</p>
</div>
{{-- Kategori --}}
<div class="relative overflow-hidden rounded-xl p-4 shadow-sm"
style="background: linear-gradient(135deg, {{ $violation->violationType?->category?->color ?? '#6b7280' }}, {{ $violation->violationType?->category?->color ?? '#6b7280' }}dd);">
<div class="absolute right-0 top-0 w-16 h-16 opacity-10">
<svg class="w-full h-full text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"/></svg>
</div>
<p class="text-[10px] font-semibold text-white/70 uppercase tracking-wider">Kategori</p>
<p class="text-base font-bold text-white mt-1 truncate">{{ $violation->violationType?->category?->name ?? '-' }}</p>
<p class="text-[10px] text-white/50 mt-0.5">tingkat pelanggaran</p>
</div>
{{-- Status Penanganan --}}
@php
$handleColors = [
'unhandled' => ['from' => '#dc2626', 'to' => '#b91c1c'],
'in_progress' => ['from' => '#d97706', 'to' => '#b45309'],
'resolved' => ['from' => '#16a34a', 'to' => '#15803d'],
];
$handleLabels = [
'unhandled' => 'Belum Ditangani',
'in_progress' => 'Dalam Proses',
'resolved' => 'Selesai',
];
$hcolor = $handleColors[$violation->handling_status] ?? ['from' => '#6b7280', 'to' => '#4b5563'];
@endphp
<div class="relative overflow-hidden rounded-xl p-4 shadow-sm"
style="background: linear-gradient(135deg, {{ $hcolor['from'] }}, {{ $hcolor['to'] }});">
<div class="absolute right-0 top-0 w-16 h-16 opacity-10">
<i class="fa-solid fa-hand-holding-heart text-white text-4xl"></i>
</div>
<p class="text-[10px] font-semibold text-white/70 uppercase tracking-wider">Penanganan</p>
<p class="text-base font-bold text-white mt-1">{{ $handleLabels[$violation->handling_status] ?? '-' }}</p>
<p class="text-[10px] text-white/50 mt-0.5">
{{ $violation->handlings->count() }} catatan
@if($violation->handled_at)
• {{ $violation->handled_at->format('d/m') }}
@endif
</p>
</div>
</div>
</div>
</div>
{{-- Card Detail Pelanggaran --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
<div class="px-5 py-4 border-b border-gray-100 flex items-center justify-between">
<h3 class="text-sm font-semibold text-gray-900">Detail Pelanggaran</h3>
<span class="inline-flex items-center px-2.5 py-1 text-xs font-medium rounded-full"
{{-- Detail Info Card --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
{{-- Header --}}
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between gap-3">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center shadow-sm">
<i class="fa-solid fa-gavel text-white text-sm"></i>
</div>
<div>
<h3 class="text-sm font-semibold text-gray-900">Informasi Pelanggaran</h3>
<p class="text-xs text-gray-400">Detail lengkap pelanggaran yang tercatat</p>
</div>
</div>
<span class="inline-flex items-center gap-1.5 px-3 py-1 text-xs font-medium rounded-full"
style="background-color: {{ $violation->violationType?->category?->color ?? '#6b7280' }}15; color: {{ $violation->violationType?->category?->color ?? '#6b7280' }}">
<span class="w-1.5 h-1.5 rounded-full mr-1.5" style="background-color: {{ $violation->violationType?->category?->color ?? '#6b7280' }}"></span>
<span class="w-2 h-2 rounded-full" style="background-color: {{ $violation->violationType?->category?->color ?? '#6b7280' }}"></span>
{{ $violation->violationType?->category?->name ?? '-' }}
</span>
</div>
<div class="p-5">
{{-- Info bars --}}
<div class="space-y-4">
<div class="flex items-center justify-between py-2 border-b border-gray-50 last:border-0">
<div class="flex items-center space-x-2.5">
<div class="w-8 h-8 rounded-lg bg-blue-50 flex items-center justify-center">
<i class="fa-solid fa-gavel text-blue-500 text-sm"></i>
{{-- Content Grid --}}
<div class="p-6">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
{{-- Jenis Pelanggaran --}}
<div class="bg-gradient-to-br from-gray-50 to-gray-100/50 rounded-xl border border-gray-100 p-4">
<div class="flex items-center gap-2.5 mb-2">
<div class="w-7 h-7 rounded-lg bg-blue-50 flex items-center justify-center">
<i class="fa-solid fa-gavel text-blue-500 text-xs"></i>
</div>
<span class="text-sm text-gray-600">Jenis Pelanggaran</span>
<span class="text-[11px] font-semibold text-gray-400 uppercase tracking-wider">Jenis Pelanggaran</span>
</div>
<span class="text-sm font-medium text-gray-900">{{ $violation->violationType->name ?? '-' }}</span>
<p class="text-sm font-semibold text-gray-900">{{ $violation->violationType->name ?? '-' }}</p>
</div>
<div class="flex items-center justify-between py-2 border-b border-gray-50">
<div class="flex items-center space-x-2.5">
<div class="w-8 h-8 rounded-lg bg-blue-50 flex items-center justify-center">
<i class="fa-solid fa-star text-blue-500 text-sm"></i>
{{-- Poin --}}
<div class="bg-gradient-to-br from-gray-50 to-gray-100/50 rounded-xl border border-gray-100 p-4">
<div class="flex items-center gap-2.5 mb-2">
<div class="w-7 h-7 rounded-lg flex items-center justify-center
{{ $violation->points >= 50 ? 'bg-red-50' : ($violation->points >= 15 ? 'bg-yellow-50' : 'bg-blue-50') }}">
<i class="fa-solid fa-star {{ $violation->points >= 50 ? 'text-red-500' : ($violation->points >= 15 ? 'text-yellow-500' : 'text-blue-500') }} text-xs"></i>
</div>
<span class="text-sm text-gray-600">Poin</span>
<span class="text-[11px] font-semibold text-gray-400 uppercase tracking-wider">Poin Pelanggaran</span>
</div>
<span class="text-lg font-bold text-blue-600">+{{ $violation->points }}</span>
<p class="text-sm font-bold {{ $violation->points >= 50 ? 'text-red-600' : ($violation->points >= 15 ? 'text-yellow-600' : 'text-blue-600') }}">
+{{ $violation->points }}
</p>
</div>
<div class="flex items-center justify-between py-2 border-b border-gray-50">
<div class="flex items-center space-x-2.5">
<div class="w-8 h-8 rounded-lg bg-blue-50 flex items-center justify-center">
<i class="fa-solid fa-scale-balanced text-blue-500 text-sm"></i>
{{-- Tanggal --}}
<div class="bg-gradient-to-br from-gray-50 to-gray-100/50 rounded-xl border border-gray-100 p-4">
<div class="flex items-center gap-2.5 mb-2">
<div class="w-7 h-7 rounded-lg bg-blue-50 flex items-center justify-center">
<i class="fa-solid fa-calendar text-blue-500 text-xs"></i>
</div>
<span class="text-sm text-gray-600">Sanksi</span>
<span class="text-[11px] font-semibold text-gray-400 uppercase tracking-wider">Tanggal</span>
</div>
<span class="text-sm font-medium text-gray-900 text-right max-w-[50%]">{{ $violation->sanction ?? '-' }}</span>
<p class="text-sm font-semibold text-gray-900">
{{ $violation->violation_date->format('d M Y') }}
@if($violation->violation_time)
<span class="text-gray-400">• {{ \Carbon\Carbon::parse($violation->violation_time)->format('H:i') }}</span>
@endif
</p>
</div>
<div class="flex items-center justify-between py-2 border-b border-gray-50">
<div class="flex items-center space-x-2.5">
<div class="w-8 h-8 rounded-lg bg-blue-50 flex items-center justify-center">
<i class="fa-solid fa-calendar text-blue-500 text-sm"></i>
{{-- Sanksi --}}
<div class="bg-gradient-to-br from-gray-50 to-gray-100/50 rounded-xl border border-gray-100 p-4">
<div class="flex items-center gap-2.5 mb-2">
<div class="w-7 h-7 rounded-lg bg-blue-50 flex items-center justify-center">
<i class="fa-solid fa-scale-balanced text-blue-500 text-xs"></i>
</div>
<span class="text-sm text-gray-600">Tanggal</span>
<span class="text-[11px] font-semibold text-gray-400 uppercase tracking-wider">Sanksi</span>
</div>
<span class="text-sm font-medium text-gray-900">{{ $violation->violation_date->format('d F Y') }} @if($violation->violation_time){{ \Carbon\Carbon::parse($violation->violation_time)->format('H:i') }} WIB @endif</span>
<p class="text-sm font-medium text-gray-900">{{ $violation->sanction ?? '—' }}</p>
</div>
</div>
{{-- Row 2 --}}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4">
{{-- Lokasi --}}
<div class="bg-gradient-to-br from-gray-50 to-gray-100/50 rounded-xl border border-gray-100 p-4">
<div class="flex items-center gap-2.5 mb-2">
<div class="w-7 h-7 rounded-lg bg-blue-50 flex items-center justify-center">
<i class="fa-solid fa-location-dot text-blue-500 text-xs"></i>
</div>
<span class="text-[11px] font-semibold text-gray-400 uppercase tracking-wider">Lokasi</span>
</div>
<p class="text-sm font-medium text-gray-900">{{ $violation->location ?? '—' }}</p>
</div>
<div class="flex items-center justify-between py-2 border-b border-gray-50">
<div class="flex items-center space-x-2.5">
<div class="w-8 h-8 rounded-lg bg-blue-50 flex items-center justify-center">
<i class="fa-solid fa-location-dot text-blue-500 text-sm"></i>
{{-- Pencatat --}}
<div class="bg-gradient-to-br from-gray-50 to-gray-100/50 rounded-xl border border-gray-100 p-4">
<div class="flex items-center gap-2.5 mb-2">
<div class="w-7 h-7 rounded-lg bg-blue-50 flex items-center justify-center">
<i class="fa-solid fa-user-pen text-blue-500 text-xs"></i>
</div>
<span class="text-sm text-gray-600">Lokasi</span>
<span class="text-[11px] font-semibold text-gray-400 uppercase tracking-wider">Dicatat Oleh</span>
</div>
<span class="text-sm font-medium text-gray-900">{{ $violation->location ?? '-' }}</span>
</div>
<div class="flex items-center justify-between py-2">
<div class="flex items-center space-x-2.5">
<div class="w-8 h-8 rounded-lg bg-blue-50 flex items-center justify-center">
<i class="fa-solid fa-user-pen text-blue-500 text-sm"></i>
<div class="flex items-center gap-2">
<div class="w-7 h-7 rounded-full bg-gray-200 flex items-center justify-center">
<span class="text-[10px] font-bold text-gray-500">{{ strtoupper(substr($violation->recorder->name ?? '?', 0, 1)) }}</span>
</div>
<span class="text-sm text-gray-600">Dicatat oleh</span>
<p class="text-sm font-medium text-gray-900">{{ $violation->recorder->name ?? '—' }}</p>
</div>
<span class="text-sm font-medium text-gray-900">{{ $violation->recorder->name ?? '-' }}</span>
</div>
</div>
</div>
@@ -138,82 +255,421 @@
{{-- Deskripsi --}}
@if($violation->description)
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
<div class="px-5 py-4 border-b border-gray-100">
<h3 class="text-sm font-semibold text-gray-900">Deskripsi / Catatan</h3>
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-5 sm:px-6 py-3.5 border-b border-gray-100">
<div class="flex items-center gap-2.5">
<div class="w-7 h-7 sm:w-8 sm:h-8 rounded-lg bg-blue-50 flex items-center justify-center flex-shrink-0">
<i class="fa-solid fa-note-sticky text-blue-500 text-xs sm:text-sm"></i>
</div>
<h3 class="text-sm font-semibold text-gray-900">Catatan / Deskripsi</h3>
</div>
</div>
<div class="p-5">
<p class="text-sm text-gray-700 leading-relaxed">{{ $violation->description }}</p>
<div class="px-5 sm:px-6 py-4 sm:py-5">
<div class="bg-gray-50 rounded-xl border border-gray-100 p-4 sm:p-5">
<p class="text-sm text-gray-700 leading-relaxed whitespace-pre-line">{{ $violation->description }}</p>
</div>
</div>
</div>
@endif
</div>
{{-- Right Sidebar: Status + Evidences --}}
<div class="space-y-6">
{{-- ===== RIGHT COLUMN ===== --}}
<div class="space-y-5">
{{-- Status Card --}}
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
<div class="px-5 py-4 border-b border-gray-100">
<h3 class="text-sm font-semibold text-gray-900">Status</h3>
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-blue-500 to-blue-600 flex items-center justify-center shadow-sm">
<i class="fa-solid fa-circle-info text-white text-sm"></i>
</div>
<div>
<h3 class="text-sm font-semibold text-gray-900">Status</h3>
<p class="text-xs text-gray-400">Riwayat siklus pelanggaran</p>
</div>
</div>
</div>
<div class="p-5 space-y-4">
{{-- Verifikasi --}}
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600">Verifikasi</span>
@if($violation->is_verified)
<span class="inline-flex items-center px-2.5 py-1 text-xs font-medium bg-green-100 text-green-700 rounded-full">
<i class="fa-solid fa-check-circle mr-1"></i>
Terverifikasi
</span>
@else
<span class="inline-flex items-center px-2.5 py-1 text-xs font-medium bg-yellow-100 text-yellow-700 rounded-full">
<i class="fa-solid fa-clock mr-1"></i>
Belum
</span>
@endif
<div class="p-5">
{{-- Verification Badge --}}
<div class="mb-5 p-4 rounded-xl {{ $violation->is_verified ? 'bg-gradient-to-br from-green-50 to-emerald-50/50 border border-green-200' : 'bg-gradient-to-br from-yellow-50 to-amber-50/50 border border-yellow-200' }}">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl {{ $violation->is_verified ? 'bg-green-100' : 'bg-yellow-100' }} flex items-center justify-center flex-shrink-0">
@if($violation->is_verified)
<i class="fa-solid fa-check-circle text-green-600 text-lg"></i>
@else
<i class="fa-solid fa-clock text-yellow-600 text-lg"></i>
@endif
</div>
<div>
<p class="text-sm font-bold {{ $violation->is_verified ? 'text-green-800' : 'text-yellow-800' }}">
{{ $violation->is_verified ? 'Terverifikasi' : 'Menunggu Verifikasi' }}
</p>
@if($violation->is_verified)
<p class="text-xs text-green-600 mt-0.5">
oleh <span class="font-semibold">{{ $violation->verifier->name ?? '-' }}</span>
@if($violation->verified_at)
<span class="text-green-400">• {{ $violation->verified_at->format('d M Y H:i') }}</span>
@endif
</p>
@else
<p class="text-xs text-yellow-600 mt-0.5">Belum diverifikasi oleh petugas BK</p>
@endif
</div>
</div>
</div>
@if($violation->is_verified)
<div class="text-xs text-gray-400 flex items-center space-x-1">
<i class="fa-solid fa-check text-green-500"></i>
<span>oleh {{ $violation->verifier->name ?? '-' }} • {{ $violation->verified_at?->format('d/m/Y H:i') }}</span>
</div>
@endif
{{-- Timeline --}}
<div class="relative">
{{-- Garis vertikal --}}
<div class="absolute left-[15px] top-3 bottom-3 w-0.5 bg-gray-200"></div>
{{-- Waktu input --}}
<div class="pt-3 border-t border-gray-100">
<p class="text-xs text-gray-400">Dibuat pada</p>
<p class="text-sm text-gray-700">{{ $violation->created_at->format('d F Y H:i') }}</p>
<div class="space-y-5">
{{-- Item: Verifikasi --}}
<div class="relative pl-9">
<div class="absolute left-0 top-[3px] w-[30px] flex justify-center">
<div class="w-[13px] h-[13px] rounded-full border-[3px] flex items-center justify-center"
style="border-color: {{ $violation->is_verified ? '#22c55e' : '#eab308' }}; background: white;">
<span class="w-[5px] h-[5px] rounded-full" style="background-color: {{ $violation->is_verified ? '#22c55e' : '#eab308' }}"></span>
</div>
</div>
<p class="text-sm font-semibold {{ $violation->is_verified ? 'text-green-600' : 'text-yellow-600' }}">
{{ $violation->is_verified ? 'Diverifikasi' : 'Menunggu Verifikasi' }}
</p>
@if($violation->is_verified)
<p class="text-xs text-gray-400">{{ $violation->verified_at ? $violation->verified_at->format('d M Y H:i') : '' }}</p>
@endif
</div>
{{-- Item: Dibuat --}}
<div class="relative pl-9">
<div class="absolute left-0 top-[3px] w-[30px] flex justify-center">
<div class="w-[13px] h-[13px] rounded-full border-[3px] border-blue-500 bg-white">
</div>
</div>
<p class="text-sm font-semibold text-gray-800">Dibuat</p>
<p class="text-xs text-gray-400">{{ $violation->created_at->format('d M Y H:i') }}</p>
</div>
{{-- Item: Diubah --}}
@if($violation->updated_at != $violation->created_at)
<div class="relative pl-9">
<div class="absolute left-0 top-[3px] w-[30px] flex justify-center">
<div class="w-[13px] h-[13px] rounded-full border-[3px] border-gray-300 bg-white">
</div>
</div>
<p class="text-sm font-semibold text-gray-500">Terakhir Diubah</p>
<p class="text-xs text-gray-400">{{ $violation->updated_at->format('d M Y H:i') }}</p>
</div>
@endif
</div>
</div>
@if($violation->updated_at != $violation->created_at)
<div>
<p class="text-xs text-gray-400">Terakhir diubah</p>
<p class="text-sm text-gray-700">{{ $violation->updated_at->format('d F Y H:i') }}</p>
</div>
@endif
</div>
</div>
{{-- Evidence Photos --}}
@if($violation->evidences->count() > 0)
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
<div class="px-5 py-4 border-b border-gray-100 flex items-center justify-between">
<h3 class="text-sm font-semibold text-gray-900">Bukti Foto</h3>
<span class="text-xs text-gray-400">{{ $violation->evidences->count() }} file</span>
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-5 sm:px-6 py-3.5 border-b border-gray-100 flex items-center justify-between gap-3">
<div class="flex items-center gap-2.5 min-w-0">
<div class="w-7 h-7 sm:w-8 sm:h-8 rounded-lg bg-blue-50 flex items-center justify-center flex-shrink-0">
<i class="fa-solid fa-camera text-blue-500 text-xs sm:text-sm"></i>
</div>
<h3 class="text-sm font-semibold text-gray-900 truncate">Bukti Foto</h3>
</div>
<span class="inline-flex items-center px-2 py-0.5 text-[11px] font-medium bg-gray-100 text-gray-500 rounded-full whitespace-nowrap flex-shrink-0">
<i class="fa-solid fa-image mr-1"></i>
{{ $violation->evidences->count() }}
</span>
</div>
<div class="p-5">
<div class="grid grid-cols-2 gap-3">
<div class="p-4 sm:p-5">
<div class="evidence-grid grid grid-cols-2 gap-2.5">
@foreach($violation->evidences as $evidence)
<a href="{{ $evidence->url }}" target="_blank"
class="group relative block aspect-square rounded-xl overflow-hidden border border-gray-200 bg-gray-50 transition-all">
<img src="{{ $evidence->url }}" class="w-full h-full object-cover scale-100 transition-transform duration-300" alt="{{ $evidence->original_name }}">
<div class="absolute inset-0 bg-black/0 bg-black/0 transition-colors"></div>
<a href="{{ $evidence->url }}" target="_blank" rel="noopener noreferrer"
class="group relative block aspect-square rounded-xl overflow-hidden border border-gray-200 bg-gray-50 shadow-sm">
<img src="{{ $evidence->url }}"
class="w-full h-full object-cover"
alt="{{ $evidence->original_name }}">
<div class="evidence-overlay absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors flex items-center justify-center opacity-0 group-hover:opacity-100">
<span class="inline-flex items-center gap-1 text-white text-[11px] font-medium bg-black/50 px-3 py-1.5 rounded-lg backdrop-blur-sm">
<i class="fa-solid fa-expand"></i>
Perbesar
</span>
</div>
@if($loop->first && $violation->evidences->count() > 1)
<span class="absolute top-2 left-2 text-[10px] font-medium text-white bg-black/40 px-2 py-0.5 rounded-md backdrop-blur-sm">{{ $loop->iteration }} of {{ $violation->evidences->count() }}</span>
@endif
</a>
@endforeach
</div>
</div>
</div>
@endif
{{-- Link to Student Profile --}}
<a href="{{ route('students.show', $violation->student_id) }}"
class="flex items-center justify-between gap-3 p-4 bg-blue-50/60 border border-blue-100 rounded-2xl hover:bg-blue-50 transition group">
<div class="flex items-center gap-3 min-w-0">
<div class="w-9 h-9 rounded-xl bg-blue-100 flex items-center justify-center flex-shrink-0">
<i class="fa-solid fa-user-graduate text-blue-600 text-sm"></i>
</div>
<div class="min-w-0">
<p class="text-xs font-semibold text-blue-700">Lihat Profil Siswa</p>
<p class="text-[11px] text-blue-500 mt-0.5 truncate">{{ $violation->student->full_name ?? '' }}</p>
</div>
</div>
<i class="fa-solid fa-arrow-right text-blue-400 group-hover:text-blue-600 transition flex-shrink-0"></i>
</a>
{{-- Penanganan Section --}}
<div x-data="{ showHandlingModal: false }" class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-5 sm:px-6 py-3.5 border-b border-gray-100 flex items-center justify-between gap-2">
<div class="flex items-center gap-2.5 min-w-0">
<div class="w-7 h-7 sm:w-8 sm:h-8 rounded-lg bg-gradient-to-br from-amber-500 to-orange-500 flex items-center justify-center flex-shrink-0">
<i class="fa-solid fa-hand-holding-heart text-white text-xs sm:text-sm"></i>
</div>
<div class="min-w-0">
<h3 class="text-sm font-semibold text-gray-900 truncate">Penanganan</h3>
<p class="text-xs text-gray-400">{{ $violation->handlings->count() }} catatan</p>
</div>
</div>
<button @click="showHandlingModal = true"
class="inline-flex items-center gap-1 px-2.5 py-1.5 text-xs font-semibold text-blue-600 bg-blue-50 border border-blue-200 rounded-lg hover:bg-blue-100 transition flex-shrink-0 shadow-sm">
<i class="fa-solid fa-plus text-[10px]"></i>
<span class="hidden sm:inline">Tambah</span>
</button>
</div>
<div class="divide-y divide-gray-50">
@forelse($violation->handlings as $h)
<div class="p-4 sm:p-5 space-y-3">
{{-- Header --}}
<div class="flex items-start justify-between gap-3">
<div class="flex items-center gap-2.5 min-w-0">
<div class="w-7 h-7 rounded-lg bg-amber-50 flex items-center justify-center flex-shrink-0">
<i class="fa-solid fa-clipboard-list text-amber-500 text-xs"></i>
</div>
<div class="min-w-0">
<p class="text-sm font-semibold text-gray-900">{{ $h->handling_type }}</p>
<p class="text-xs text-gray-400">{{ $h->handling_date->format('d M Y') }}
@if($h->location) • {{ $h->location }} @endif
</p>
</div>
</div>
<form action="{{ route('violations.handling.destroy', [$violation->id, $h->id]) }}" method="POST"
x-data x-on:submit.prevent="if(await window.confirmSwal({text:'Hapus catatan penanganan ini?'})) $el.submit()">
@csrf @method('DELETE')
<button type="submit"
class="text-gray-300 hover:text-red-500 transition flex-shrink-0">
<i class="fa-solid fa-trash-can text-xs"></i>
</button>
</form>
</div>
{{-- Description --}}
@if($h->description)
<div class="bg-gray-50 rounded-lg border border-gray-100 p-3">
<p class="text-xs text-gray-700 leading-relaxed whitespace-pre-line">{{ $h->description }}</p>
</div>
@endif
{{-- Participants --}}
@if($h->participants->count() > 0)
<div>
<p class="text-[11px] font-semibold text-gray-400 uppercase tracking-wider mb-2">Peserta</p>
<div class="space-y-1.5">
@foreach($h->participants as $p)
<div class="flex items-center gap-2">
<div class="w-6 h-6 rounded-full bg-gray-100 flex items-center justify-center flex-shrink-0">
<span class="text-[9px] font-bold text-gray-500">{{ strtoupper(substr($p->user->name ?? '?', 0, 1)) }}</span>
</div>
<span class="text-xs text-gray-700 font-medium">{{ $p->user->name ?? '-' }}</span>
@if($p->role)
<span class="text-[10px] text-gray-400 bg-gray-50 px-1.5 py-0.5 rounded border border-gray-100">{{ $p->role }}</span>
@endif
</div>
@endforeach
</div>
</div>
@endif
{{-- Evidence --}}
@if($h->evidence)
<a href="{{ \Storage::url($h->evidence) }}" target="_blank"
class="inline-flex items-center gap-1.5 text-xs text-blue-600 bg-blue-50 px-2.5 py-1.5 rounded-lg border border-blue-100 hover:bg-blue-100 transition">
<i class="fa-solid fa-paperclip text-[10px]"></i>
Lihat Bukti
</a>
@endif
{{-- Creator --}}
@if($h->creator)
<p class="text-[10px] text-gray-400">Dicatat oleh {{ $h->creator->name }}</p>
@endif
</div>
@empty
<div class="px-5 py-8 text-center">
<div class="w-10 h-10 rounded-full bg-gray-50 border border-gray-100 flex items-center justify-center mx-auto mb-2">
<i class="fa-solid fa-hand-holding-heart text-gray-300 text-sm"></i>
</div>
<p class="text-xs text-gray-500">Belum ada penanganan</p>
</div>
@endforelse
</div>
@if($violation->handlings->count() > 0 && $violation->isInProgress())
<div class="px-5 py-3 border-t border-gray-100 bg-gray-50/50">
<form action="{{ route('violations.resolve', $violation->id) }}" method="POST">
@csrf
<button type="submit"
class="w-full inline-flex items-center justify-center gap-1.5 px-3 py-2 text-xs font-semibold text-green-700 bg-green-50 border border-green-200 rounded-xl hover:bg-green-100 transition">
<i class="fa-solid fa-check-circle text-xs"></i>
Tandai Selesai Ditangani
</button>
</form>
</div>
@endif
{{-- Add Handling Modal --}}
<div x-show="showHandlingModal"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 z-50 overflow-y-auto" style="display: none;">
<div class="flex items-center justify-center min-h-screen px-4 py-4">
<div class="fixed inset-0 bg-gray-900/40 backdrop-blur-sm"></div>
<div class="relative bg-white rounded-2xl shadow-xl border border-gray-200 w-full max-w-lg mx-4 max-h-[90vh] overflow-y-auto">
<div class="px-6 py-4 border-b border-gray-100 flex items-center justify-between sticky top-0 bg-white z-10">
<div class="flex items-center gap-3">
<div class="w-9 h-9 rounded-xl bg-gradient-to-br from-amber-500 to-orange-500 flex items-center justify-center shadow-sm">
<i class="fa-solid fa-hand-holding-heart text-white text-sm"></i>
</div>
<div>
<h3 class="text-sm font-semibold text-gray-900">Tambah Penanganan</h3>
<p class="text-xs text-gray-400">{{ $violation->student->full_name ?? '-' }}</p>
</div>
</div>
<button @click="showHandlingModal = false" class="w-8 h-8 rounded-lg flex items-center justify-center text-gray-400 hover:text-gray-600 hover:bg-gray-100 transition">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
<form action="{{ route('violations.handling.store', $violation->id) }}" method="POST" enctype="multipart/form-data" class="p-6"
x-data="{ participants: [] }">
@csrf
<div class="space-y-4">
{{-- Jenis Penanganan --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Jenis Penanganan <span class="text-red-500">*</span></label>
<select name="handling_type" required
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
<option value="">Pilih jenis</option>
<option value="Teguran Lisan">Teguran Lisan</option>
<option value="Teguran Tertulis">Teguran Tertulis</option>
<option value="Pembinaan BK">Pembinaan BK</option>
<option value="Panggilan Orang Tua">Panggilan Orang Tua</option>
<option value="Home Visit">Home Visit</option>
<option value="Skorsing">Skorsing</option>
<option value="Tugas Sosial">Tugas Sosial</option>
<option value="SP-1">SP-1</option>
<option value="SP-2">SP-2</option>
<option value="SP-3">SP-3</option>
<option value="Lainnya">Lainnya</option>
</select>
</div>
{{-- Tanggal --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Tanggal Penanganan <span class="text-red-500">*</span></label>
<input type="date" name="handling_date" value="{{ date('Y-m-d') }}" required
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
{{-- Lokasi --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Lokasi</label>
<input type="text" name="location" placeholder="Ruang BK, Ruang Guru, dll"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
</div>
{{-- Deskripsi --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Catatan / Deskripsi</label>
<textarea name="description" rows="3" placeholder="Catatan penanganan..."
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition"></textarea>
</div>
{{-- Bukti --}}
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Bukti (opsional)</label>
<input type="file" name="evidence" accept="image/*,.pdf,.doc,.docx"
class="w-full text-sm text-gray-500 file:mr-3 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-medium file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100 transition">
</div>
{{-- Participants --}}
<div>
<div class="flex items-center justify-between mb-1">
<label class="block text-sm font-medium text-gray-700">Peserta</label>
<button type="button" @click="participants.push({user_id: '', role: ''})"
class="text-xs font-semibold text-blue-600 hover:text-blue-800 transition inline-flex items-center gap-1">
<i class="fa-solid fa-plus"></i> Tambah Peserta
</button>
</div>
<div class="space-y-2">
<template x-for="(p, i) in participants" :key="i">
<div class="flex items-center gap-2">
<select :name="'participants['+i+'][user_id]'" x-model="p.user_id"
class="flex-1 px-3 py-2 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
<option value="">Pilih petugas...</option>
@foreach($users as $user)
<option value="{{ $user->id }}">{{ $user->name }} ({{ $user->role }})</option>
@endforeach
</select>
<select :name="'participants['+i+'][role]'" x-model="p.role"
class="w-[140px] px-3 py-2 border border-gray-200 rounded-xl text-sm bg-gray-50/50 focus:bg-white focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition">
<option value="">Peran</option>
<option value="Wakil Ketua Tim">Wakil Ketua Tim</option>
<option value="Anggota Tim">Anggota Tim</option>
<option value="Wali Kelas">Wali Kelas</option>
<option value="Guru BK">Guru BK</option>
<option value="Saksi">Saksi</option>
</select>
<button type="button" @click="participants.splice(i, 1)"
class="p-2 text-gray-300 hover:text-red-500 transition flex-shrink-0">
<i class="fa-solid fa-xmark"></i>
</button>
</div>
</template>
</div>
</div>
</div>
<div class="flex items-center justify-end gap-3 mt-6 pt-4 border-t border-gray-100">
<button type="button" @click="showHandlingModal = false"
class="px-5 py-2.5 text-sm font-medium text-gray-600 bg-white border border-gray-200 rounded-xl hover:bg-gray-50 transition">
Batal
</button>
<button type="submit"
class="px-5 py-2.5 text-sm font-semibold text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm inline-flex items-center gap-2">
<i class="fa-solid fa-floppy-disk text-xs"></i>
Simpan
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{{-- Back to List --}}
<a href="{{ route('violations.index') }}"
class="flex items-center justify-center gap-2 p-3 text-sm text-gray-500 bg-white border border-gray-200 rounded-2xl hover:bg-gray-50 hover:text-gray-700 transition group">
<i class="fa-solid fa-arrow-left text-xs"></i>
Kembali ke Data Pelanggaran
</a>
</div>
</div>
</div>
+6
View File
@@ -28,6 +28,7 @@ Route::middleware('auth')->group(function () {
// Notifications
Route::get('/notifications/unread-count', [DashboardController::class, 'getUnreadCount'])->name('notifications.unread-count');
Route::get('/calendar/data', [DashboardController::class, 'getCalendarData'])->name('calendar.data');
Route::get('/notifications/recent', [DashboardController::class, 'getRecentNotifications'])->name('notifications.recent');
Route::post('/notifications/{id}/read', [DashboardController::class, 'markNotificationRead'])->name('notifications.read');
Route::post('/notifications/read-all', [DashboardController::class, 'markAllNotificationsRead'])->name('notifications.read-all');
@@ -40,11 +41,15 @@ Route::middleware('auth')->group(function () {
Route::get('/violations/{violation}', [ViolationController::class, 'show'])->name('violations.show');
Route::post('/violations/{violation}/verify', [ViolationController::class, 'verify'])->name('violations.verify');
Route::delete('/violations/{violation}', [ViolationController::class, 'destroy'])->name('violations.destroy');
Route::post('/violations/{violation}/handling', [ViolationController::class, 'storeHandling'])->name('violations.handling.store');
Route::post('/violations/{violation}/resolve', [ViolationController::class, 'resolveHandling'])->name('violations.resolve');
Route::delete('/violations/{violation}/handling/{handling}', [ViolationController::class, 'destroyHandling'])->name('violations.handling.destroy');
Route::get('/api/students/search', [ViolationController::class, 'searchStudents'])->name('api.students.search');
// Students
Route::get('/students', [StudentReportController::class, 'index'])->name('students.index');
Route::get('/students/{student}', [StudentReportController::class, 'show'])->name('students.show');
Route::put('/students/{student}', [StudentReportController::class, 'update'])->name('students.update');
// SP Letters
Route::get('/sp-letters', [SpLetterController::class, 'index'])->name('sp-letters.index');
@@ -68,6 +73,7 @@ Route::middleware('auth')->group(function () {
// Import
Route::get('/settings/import/template', [ImportController::class, 'downloadTemplate'])->name('settings.import.template');
Route::get('/settings/export/violation-types', [ImportController::class, 'exportViolationTypes'])->name('settings.export.violation-types');
Route::post('/settings/import/violation-types', [ImportController::class, 'importViolationTypes'])->name('settings.import.violation-types');
Route::get('/settings/thresholds', [MasterDataController::class, 'thresholds'])->name('settings.thresholds');