system-locked violation types (alpha & terlambat) + proporsional poin alpha per jam

- Migration: add is_system column to violation_types, mark alpha & terlambat
- DestroyType guard: system types cannot be deleted
- UpdateType guard: slug tetap walau nama diubah untuk system types
- UI: delete button hidden, badge lock untuk system types
- Seeder: ViolationTypeDefaultsSeeder untuk fresh deploy
- AttendanceController: lookup by slug (alpha/terlambat) instead of fuzzy match
- Alpha auto-violation: poin proporsional per jam (round((points/10)*count), min 1)
- Terlambat auto-violation: poin fix sesuai jenis (2 poin)
- Also handles both A (alpha) and T (terlambat) statuses for auto-violations
This commit is contained in:
Bro
2026-07-22 23:55:27 +07:00
parent 4bc3a84203
commit 31ef9c0b0b
19 changed files with 1674 additions and 51 deletions
+199
View File
@@ -0,0 +1,199 @@
@extends('layouts.app')
@section('title', 'Backup Database')
@section('content')
<div>
{{-- Header --}}
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<nav class="flex items-center gap-1.5 text-sm text-gray-400 mb-1">
<a href="{{ route('settings.index') }}" class="hover:text-gray-600 transition">Pengaturan</a>
<span class="text-gray-300">/</span>
<span class="text-gray-700 font-medium">Backup Database</span>
</nav>
<h1 class="text-2xl font-bold text-gray-900 tracking-tight">Backup Database</h1>
<p class="text-sm text-gray-500 mt-1">Kelola backup dan restore database aplikasi</p>
</div>
<form action="{{ route('settings.backup.create') }}" method="POST">
@csrf
<button type="submit"
class="inline-flex items-center gap-2 px-4 py-2.5 text-sm font-semibold text-white bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl hover:from-blue-600 hover:to-blue-700 transition shadow-sm">
<i class="fa-solid fa-database text-xs"></i>
Backup Sekarang
</button>
</form>
</div>
{{-- 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-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-layer-group text-white text-6xl"></i>
</div>
<div class="relative z-10">
<p class="text-xs font-semibold text-white/70 uppercase tracking-wider">Total Backup</p>
<p class="text-3xl font-bold text-white mt-1">{{ $stats['total_backups'] }}</p>
<p class="text-[10px] text-white/50 mt-0.5">file tersimpan</p>
</div>
</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-clock-rotate-left text-white text-6xl"></i>
</div>
<div class="relative z-10">
<p class="text-xs font-semibold text-white/70 uppercase tracking-wider">Backup Terakhir</p>
<p class="text-base font-bold text-white mt-1">{{ $stats['latest_backup'] }}</p>
<p class="text-[10px] text-white/50 mt-0.5">terbaru</p>
</div>
</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-hard-drive text-white text-6xl"></i>
</div>
<div class="relative z-10">
<p class="text-xs font-semibold text-white/70 uppercase tracking-wider">Total Ukuran</p>
<p class="text-3xl font-bold text-white mt-1">{{ $stats['total_size'] }}</p>
<p class="text-[10px] text-white/50 mt-0.5">semua backup</p>
</div>
</div>
<div class="relative overflow-hidden rounded-2xl bg-gradient-to-br from-amber-500 to-orange-600 p-5 shadow-sm">
<div class="absolute right-0 top-0 w-20 h-20 opacity-10">
<i class="fa-solid fa-server text-white text-6xl"></i>
</div>
<div class="relative z-10">
<p class="text-xs font-semibold text-white/70 uppercase tracking-wider">Ukuran Database</p>
<p class="text-3xl font-bold text-white mt-1">{{ $stats['db_size'] }}</p>
<p class="text-[10px] text-white/50 mt-0.5">saat ini</p>
</div>
</div>
</div>
{{-- Daftar Backup --}}
<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 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-clock-rotate-left text-white text-sm"></i>
</div>
<div>
<h3 class="text-sm font-semibold text-gray-900">Riwayat Backup</h3>
<p class="text-xs text-gray-400">Daftar file backup database</p>
</div>
</div>
</div>
@if($backups->count() > 0)
<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">Nama File</th>
<th class="px-5 py-3.5 text-left text-xs font-semibold text-gray-400 uppercase tracking-wider hidden sm:table-cell">Tanggal</th>
<th class="px-5 py-3.5 text-right text-xs font-semibold text-gray-400 uppercase tracking-wider">Ukuran</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-50">
@foreach($backups as $backup)
<tr class="hover:bg-gray-50/50 transition">
<td class="px-5 py-4 whitespace-nowrap">
<div class="flex items-center gap-2.5">
<div class="w-8 h-8 rounded-lg bg-blue-50 flex items-center justify-center flex-shrink-0">
<i class="fa-solid fa-file-zipper text-blue-500 text-xs"></i>
</div>
<div class="min-w-0">
<p class="text-sm font-medium text-gray-900 truncate max-w-[240px] sm:max-w-[300px]">{{ $backup->filename }}</p>
<p class="text-xs text-gray-400 sm:hidden">{{ $backup->date }}</p>
</div>
</div>
</td>
<td class="px-5 py-4 whitespace-nowrap hidden sm:table-cell">
<span class="text-sm text-gray-600">{{ $backup->date }}</span>
</td>
<td class="px-5 py-4 whitespace-nowrap text-right">
<span class="inline-flex items-center px-2.5 py-1 text-xs font-medium bg-gray-100 text-gray-600 rounded-lg">
<i class="fa-solid fa-hard-drive mr-1 text-gray-400"></i>
{{ $backup->size }}
</span>
</td>
<td class="px-5 py-4 whitespace-nowrap text-right">
<div class="flex items-center justify-end gap-1">
<a href="{{ route('settings.backup.download', $backup->filename) }}"
class="p-2 rounded-lg text-gray-400 hover:text-blue-600 hover:bg-blue-50 transition"
title="Download">
<i class="fa-solid fa-download"></i>
</a>
@if($loop->first)
<form action="{{ route('settings.backup.restore') }}" method="POST" id="restore-form-{{ $loop->index }}">
@csrf
<input type="hidden" name="filename" value="{{ $backup->filename }}">
<button type="button"
x-data
x-on:click="if(await window.confirmSwal({title:'Restore Database?',text:'Semua data saat ini akan diganti dengan data backup {{ $backup->filename }}. Backup otomatis akan dibuat sebelum restore.',icon:'warning',confirmText:'Ya, Restore!',cancelText:'Batal'})) document.getElementById('restore-form-{{ $loop->index }}').submit()"
class="p-2 rounded-lg text-gray-400 hover:text-amber-600 hover:bg-amber-50 transition" title="Restore">
<i class="fa-solid fa-rotate-left"></i>
</button>
</form>
@endif
<form action="{{ route('settings.backup.destroy', $backup->filename) }}" method="POST" id="delete-form-{{ $loop->index }}">
@csrf @method('DELETE')
<button type="button"
x-data
x-on:click="if(await window.confirmSwal({title:'Hapus Backup?',text:'Yakin ingin menghapus {{ $backup->filename }}?',icon:'question',confirmText:'Ya, Hapus!',cancelText:'Batal'})) document.getElementById('delete-form-{{ $loop->index }}').submit()"
class="p-2 rounded-lg text-gray-400 hover:text-red-600 hover:bg-red-50 transition" title="Hapus">
<i class="fa-solid fa-trash-can"></i>
</button>
</form>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<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>{{ $backups->count() }} file backup</span>
<span>Total {{ $stats['total_size'] }}</span>
</div>
@else
<div class="px-5 py-12 text-center">
<div class="w-16 h-16 rounded-2xl bg-gray-50 border border-gray-100 flex items-center justify-center mx-auto mb-4">
<i class="fa-solid fa-database text-gray-300 text-2xl"></i>
</div>
<h4 class="text-sm font-semibold text-gray-500 mb-1">Belum Ada Backup</h4>
<p class="text-xs text-gray-400 mb-4">Backup database pertama Anda untuk mengamankan data</p>
<form action="{{ route('settings.backup.create') }}" method="POST">
@csrf
<button type="submit"
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-semibold text-blue-600 bg-blue-50 border border-blue-200 rounded-xl hover:bg-blue-100 transition shadow-sm">
<i class="fa-solid fa-database text-xs"></i>
Buat Backup Sekarang
</button>
</form>
</div>
@endif
</div>
{{-- Info Card --}}
<div class="mt-6 bg-gradient-to-br from-blue-50 to-indigo-50/50 border border-blue-100 rounded-2xl p-5">
<div class="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 text-sm"></i>
</div>
<div>
<h4 class="text-sm font-bold text-blue-800 mb-1">Informasi Backup</h4>
<ul class="text-xs text-blue-700 space-y-1">
<li><i class="fa-solid fa-check mr-1.5 text-blue-500"></i>Backup disimpan di <code class="bg-blue-100 px-1 rounded">storage/app/backups/</code></li>
<li><i class="fa-solid fa-check mr-1.5 text-blue-500"></i>Format file: <code class="bg-blue-100 px-1 rounded">.sql.gz</code> (SQL + kompresi Gzip)</li>
<li><i class="fa-solid fa-check mr-1.5 text-blue-500"></i>Restore akan membuat backup otomatis sebelum menjalankan restore</li>
<li><i class="fa-solid fa-check mr-1.5 text-blue-500"></i>Hanya admin yang dapat mengakses fitur ini</li>
<li><i class="fa-solid fa-triangle-exclamation mr-1.5 text-amber-500"></i>Pastikan ada cukup ruang penyimpanan di server</li>
</ul>
</div>
</div>
</div>
</div>
@endsection
+37
View File
@@ -82,5 +82,42 @@
</button>
</form>
</div>
{{-- Backup Database Card --}}
<div class="mt-6 bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<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-database text-white text-sm"></i>
</div>
<div>
<h3 class="text-sm font-semibold text-gray-900">Backup Database</h3>
<p class="text-xs text-gray-400">Kelola backup dan restore database</p>
</div>
</div>
<a href="{{ route('settings.backup') }}"
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">
Kelola Backup
<i class="fa-solid fa-arrow-right text-[10px]"></i>
</a>
</div>
<div class="p-5 text-xs text-gray-500 leading-relaxed">
<p>Fitur backup database memungkinkan Anda untuk:</p>
<ul class="mt-2 space-y-1.5">
<li class="flex items-start gap-2">
<i class="fa-solid fa-check text-emerald-500 mt-0.5"></i>
Membuat backup database kapan saja
</li>
<li class="flex items-start gap-2">
<i class="fa-solid fa-check text-emerald-500 mt-0.5"></i>
Mendownload file backup untuk penyimpanan eksternal
</li>
<li class="flex items-start gap-2">
<i class="fa-solid fa-check text-emerald-500 mt-0.5"></i>
Merestore database dari backup dengan safety backup otomatis
</li>
</ul>
</div>
</div>
</div>
@endsection
+20
View File
@@ -159,6 +159,26 @@
</div>
</label>
{{-- Penanganan --}}
<label class="reset-card relative flex items-start gap-3 p-4 rounded-xl border border-gray-200 cursor-pointer hover:border-gray-300 has-[:checked]:border-amber-300 has-[:checked]:bg-amber-50/50 transition-all select-none">
<input type="checkbox" name="reset_items[]" value="handlings" class="reset-checkbox mt-0.5 w-4 h-4 rounded border-gray-300 text-amber-500 focus:ring-amber-400 focus:ring-offset-0 flex-shrink-0">
<div class="min-w-0">
<p class="text-sm font-semibold text-gray-900">Riwayat Penanganan</p>
<p class="text-xs text-gray-400 mt-0.5">{{ $stats['handlings'] }} catatan penanganan</p>
<p class="text-xs text-gray-400">Status pelanggaran akan dikembalikan ke "Belum Ditangani"</p>
</div>
</label>
{{-- Backup Files --}}
<label class="reset-card relative flex items-start gap-3 p-4 rounded-xl border border-gray-200 cursor-pointer hover:border-gray-300 has-[:checked]:border-sky-300 has-[:checked]:bg-sky-50/50 transition-all select-none">
<input type="checkbox" name="reset_items[]" value="backups" class="reset-checkbox mt-0.5 w-4 h-4 rounded border-gray-300 text-sky-500 focus:ring-sky-400 focus:ring-offset-0 flex-shrink-0">
<div class="min-w-0">
<p class="text-sm font-semibold text-gray-900">File Backup</p>
<p class="text-xs text-gray-400 mt-0.5">{{ $stats['backups'] }} file backup database</p>
<p class="text-xs text-red-500 font-medium mt-1">Tidak bisa dikembalikan!</p>
</div>
</label>
{{-- User Lain --}}
<label class="reset-card relative flex items-start gap-3 p-4 rounded-xl border border-gray-200 cursor-pointer hover:border-gray-300 has-[:checked]:border-red-300 has-[:checked]:bg-red-50/50 transition-all select-none">
<input type="checkbox" name="reset_items[]" value="users" class="reset-checkbox mt-0.5 w-4 h-4 rounded border-gray-300 text-red-500 focus:ring-red-400 focus:ring-offset-0 flex-shrink-0">
+203 -33
View File
@@ -1,54 +1,224 @@
@extends('layouts.app')
@section('title', 'Ambang Surat Peringatan')
@section('title', 'Ambang SP')
@section('content')
<div>
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900">Ambang Surat Peringatan</h1>
<p class="text-sm text-gray-500">Atur batas poin untuk setiap tingkat Surat Peringatan</p>
{{-- Header --}}
<div class="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<nav class="flex items-center gap-1.5 text-sm text-gray-400 mb-1">
<a href="{{ route('settings.index') }}" class="hover:text-gray-600 transition">Pengaturan</a>
<span class="text-gray-300">/</span>
<span class="text-gray-700 font-medium">Ambang SP</span>
</nav>
<h1 class="text-2xl font-bold text-gray-900 tracking-tight">Ambang Surat Peringatan</h1>
<p class="text-sm text-gray-500 mt-1">Atur batas poin untuk setiap tingkat Surat Peringatan</p>
</div>
<button type="button" x-data @click="window.dispatchEvent(new CustomEvent('open-modal', {detail: 'create-threshold'}))"
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>
Tambah Ambang SP
</button>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
<form action="{{ route('settings.thresholds.update') }}" method="POST" class="p-6 space-y-6">
{{-- Threshold Cards --}}
@if($thresholds->count() > 0)
<form action="{{ route('settings.thresholds.update') }}" method="POST" class="space-y-4 mb-6">
@csrf @method('PUT')
@foreach($thresholds as $t)
<input type="hidden" name="thresholds[{{ $loop->index }}][id]" value="{{ $t->id }}">
<div class="p-4 rounded-lg border-2" style="border-color: {{ $t->color }}20; background-color: {{ $t->color }}08">
<div class="flex items-center space-x-2 mb-3">
<span class="w-3 h-3 rounded-full" style="background-color: {{ $t->color }}"></span>
<h3 class="text-lg font-semibold" style="color: {{ $t->color }}">{{ $t->name }}</h3>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="block text-xs font-medium text-gray-700 mb-1">Nama</label>
<input type="text" name="thresholds[{{ $loop->index }}][name]" value="{{ $t->name }}"
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>
<label class="block text-xs font-medium text-gray-700 mb-1">Minimal Poin</label>
<input type="number" name="thresholds[{{ $loop->index }}][min_points]" value="{{ $t->min_points }}" min="0"
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>
<label class="block text-xs font-medium text-gray-700 mb-1">Maksimal Poin (kosongkan = tak terbatas)</label>
<input type="number" name="thresholds[{{ $loop->index }}][max_points]" value="{{ $t->max_points }}" min="0"
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>
<label class="block text-xs font-medium text-gray-700 mb-1">Deskripsi</label>
<input type="text" name="thresholds[{{ $loop->index }}][default_description]" value="{{ $t->default_description }}"
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 class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden transition hover:shadow-md">
<div class="flex items-stretch">
{{-- Left color bar --}}
<div class="w-[5px] flex-shrink-0" style="background-color: {{ $t->color }}"></div>
<div class="flex-1 px-5 py-4">
{{-- Header --}}
<div class="flex items-start justify-between gap-4 mb-4">
<div class="flex items-center gap-3 min-w-0">
<div class="w-9 h-9 rounded-xl flex items-center justify-center shadow-sm flex-shrink-0"
style="background-color: {{ $t->color }}20; color: {{ $t->color }}">
<i class="fa-solid fa-file-lines text-sm"></i>
</div>
<div class="min-w-0">
<div class="flex items-center gap-2">
<h3 class="text-sm font-bold text-gray-900" style="color: {{ $t->color }}">{{ $t->name }}</h3>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" name="thresholds[{{ $loop->index }}][is_active]" value="1"
{{ $t->is_active ? 'checked' : '' }}
class="sr-only peer">
<div class="w-8 h-4 bg-gray-200 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-3 after:w-3 after:transition-all"
style="{{ $t->is_active ? 'background-color: ' . $t->color : '' }}"></div>
</label>
</div>
<p class="text-xs text-gray-400 mt-0.5">Min {{ $t->min_points }} poin
@if($t->max_points) – Maks {{ $t->max_points }} poin @else + (tak terbatas) @endif
</p>
</div>
</div>
<div class="flex items-center gap-1 flex-shrink-0">
<form action="{{ route('settings.thresholds.destroy', $t->id) }}" method="POST" id="delete-threshold-{{ $t->id }}">
@csrf @method('DELETE')
<button type="button"
x-data
x-on:click="if(await window.confirmSwal({title:'Hapus {{ $t->name }}?',text:'Yakin ingin menghapus ambang SP ini?',icon:'question',confirmText:'Ya, Hapus!',cancelText:'Batal'})) document.getElementById('delete-threshold-{{ $t->id }}').submit()"
class="w-7 h-7 rounded-lg flex items-center justify-center text-gray-300 hover:text-red-500 hover:bg-red-50 transition"
title="Hapus">
<i class="fa-solid fa-trash-can text-xs"></i>
</button>
</form>
</div>
</div>
{{-- Fields --}}
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
<div>
<label class="block text-[11px] font-semibold text-gray-400 uppercase tracking-wider mb-1">Nama</label>
<input type="text" name="thresholds[{{ $loop->index }}][name]" value="{{ $t->name }}"
class="w-full px-3.5 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">
</div>
<div>
<label class="block text-[11px] font-semibold text-gray-400 uppercase tracking-wider mb-1">Min Poin</label>
<input type="number" name="thresholds[{{ $loop->index }}][min_points]" value="{{ $t->min_points }}" min="0"
class="w-full px-3.5 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">
</div>
<div>
<label class="block text-[11px] font-semibold text-gray-400 uppercase tracking-wider mb-1">Maks Poin</label>
<input type="number" name="thresholds[{{ $loop->index }}][max_points]" value="{{ $t->max_points }}" min="0" placeholder="Tak terbatas"
class="w-full px-3.5 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">
</div>
<div>
<label class="block text-[11px] font-semibold text-gray-400 uppercase tracking-wider mb-1">Deskripsi</label>
<input type="text" name="thresholds[{{ $loop->index }}][default_description]" value="{{ $t->default_description }}"
class="w-full px-3.5 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">
</div>
</div>
</div>
</div>
</div>
@endforeach
<button type="submit" class="w-full px-4 py-2.5 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition">
Simpan Perubahan
<button type="submit"
class="w-full px-5 py-3 text-sm font-semibold text-white bg-blue-600 rounded-xl hover:bg-blue-700 transition shadow-sm inline-flex items-center justify-center gap-2">
<i class="fa-solid fa-floppy-disk text-xs"></i>
Simpan Semua Perubahan
</button>
</form>
@else
{{-- Empty state --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 py-12 text-center">
<div class="w-16 h-16 rounded-2xl bg-gray-50 border border-gray-100 flex items-center justify-center mx-auto mb-4">
<i class="fa-solid fa-chart-bar text-gray-300 text-2xl"></i>
</div>
<h4 class="text-sm font-semibold text-gray-500 mb-1">Belum Ada Ambang SP</h4>
<p class="text-xs text-gray-400 mb-4">Tambahkan ambang Surat Peringatan untuk memantau level pelanggaran siswa</p>
<button type="button" x-data @click="window.dispatchEvent(new CustomEvent('open-modal', {detail: 'create-threshold'}))"
class="inline-flex items-center gap-2 px-4 py-2 text-sm font-semibold text-blue-600 bg-blue-50 border border-blue-200 rounded-xl hover:bg-blue-100 transition shadow-sm">
<i class="fa-solid fa-plus text-xs"></i>
Tambah Ambang SP
</button>
</div>
@endif
{{-- Create Modal --}}
<div x-data="{ open: false }"
x-on:open-modal.window="if($event.detail === 'create-threshold') open = true"
x-show="open"
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">
<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-plus text-white text-sm"></i>
</div>
<div>
<h3 class="text-sm font-semibold text-gray-900">Tambah Ambang SP</h3>
<p class="text-xs text-gray-400">Batas poin Surat Peringatan baru</p>
</div>
</div>
<button @click="open = 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('settings.thresholds.store') }}" method="POST" class="p-6">
@csrf
<div class="space-y-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Nama Threshold <span class="text-red-500">*</span></label>
<input type="text" name="name" placeholder="SP 4, SP 5, dll" 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 class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-1">Minimal Poin <span class="text-red-500">*</span></label>
<input type="number" name="min_points" min="0" value="200" 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-semibold text-gray-700 mb-1">Maksimal Poin</label>
<input type="number" name="max_points" min="0" placeholder="Tak terbatas"
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>
<label class="block text-sm font-semibold text-gray-700 mb-1">Deskripsi</label>
<input type="text" name="default_description" placeholder="Contoh: SP 4 — poin mencapai 200"
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-semibold text-gray-700 mb-1">Warna</label>
<div class="flex items-center gap-3">
<input type="color" name="color" value="#8b5cf6"
class="w-10 h-10 rounded-lg border border-gray-200 cursor-pointer p-0.5">
<span class="text-xs text-gray-400">Pilih warna identitas threshold</span>
</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="open = 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>
{{-- Info Card --}}
<div class="mt-6 bg-gradient-to-br from-blue-50 to-indigo-50/50 border border-blue-100 rounded-2xl p-5">
<div class="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 text-sm"></i>
</div>
<div>
<h4 class="text-sm font-bold text-blue-800 mb-1">Cara Kerja Ambang SP</h4>
<ul class="text-xs text-blue-700 space-y-1">
<li><i class="fa-solid fa-check mr-1.5 text-blue-500"></i>SP1 (≥50 poin) — peringatan pertama</li>
<li><i class="fa-solid fa-check mr-1.5 text-blue-500"></i>SP2 (≥100 poin) — peringatan kedua</li>
<li><i class="fa-solid fa-check mr-1.5 text-blue-500"></i>SP3 (≥150 poin) — peringatan ketiga</li>
<li><i class="fa-solid fa-star mr-1.5 text-amber-500"></i>Setiap poin dihitung dari akumulasi semua pelanggaran siswa</li>
<li><i class="fa-solid fa-star mr-1.5 text-amber-500"></i>Toggle aktif/nonaktif untuk mengontrol threshold yang digunakan</li>
<li><i class="fa-solid fa-star mr-1.5 text-amber-500"></i>Threshold baru otomatis aktif saat dibuat</li>
</ul>
</div>
</div>
</div>
</div>
@endsection
@@ -162,14 +162,21 @@
</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()">
@csrf @method('DELETE')
<button type="submit"
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>
@if (!$type->is_system)
<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()">
@csrf @method('DELETE')
<button type="submit"
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>
@else
<span class="inline-flex items-center gap-1 px-2 py-1 text-xs font-medium text-blue-600 bg-blue-50 border border-blue-200 rounded-lg">
<i class="fa-solid fa-lock text-[10px]"></i>
<span class="hidden sm:inline">Sistem</span>
</span>
@endif
</div>
</td>
</tr>