feat: Face ID (scan, register, liveness) + role baru KS/Waka/Ketua Tim + notifikasi WA ortu + scoping wali kelas + error pages premium

This commit is contained in:
Novy San87
2026-08-02 20:57:11 +07:00
parent 0a5f471854
commit 6e256661dd
76 changed files with 5335 additions and 888 deletions
+198
View File
@@ -54,6 +54,78 @@
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
{{-- Grafik Tren Pelanggaran --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden" x-data="trendToggle()">
<div class="flex flex-wrap items-center justify-between gap-3 px-5 py-4 border-b border-gray-100">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-blue-500 to-sky-400 flex items-center justify-center text-white shadow-sm">
<i class="fa-solid fa-chart-line"></i>
</div>
<div>
<h3 class="text-sm font-bold text-gray-800">Tren Pelanggaran Siswa</h3>
<p class="text-[11px] text-gray-400">Jumlah pelanggaran per hari</p>
</div>
</div>
<div class="flex flex-wrap items-center gap-2">
<span class="text-xs font-semibold text-gray-500">Total <span x-text="period"></span> hari: <strong class="text-gray-800" x-text="period === 7 ? total7 : (period === 14 ? total14 : total30)"></strong></span>
<span class="text-xs font-semibold text-gray-500">Hari ini: <strong class="text-gray-800">{{ $dailyCurrent }}</strong></span>
@if ($trendPercent > 0)
<span class="inline-flex items-center gap-1 rounded-full bg-red-50 px-2.5 py-1 text-[11px] font-bold text-red-600 ring-1 ring-red-200">
<i class="fa-solid fa-arrow-trend-up"></i> +{{ $trendPercent }}% vs kemarin
</span>
@elseif ($trendPercent < 0)
<span class="inline-flex items-center gap-1 rounded-full bg-emerald-50 px-2.5 py-1 text-[11px] font-bold text-emerald-600 ring-1 ring-emerald-200">
<i class="fa-solid fa-arrow-trend-down"></i> {{ $trendPercent }}% vs kemarin
</span>
@else
<span class="inline-flex items-center gap-1 rounded-full bg-slate-50 px-2.5 py-1 text-[11px] font-bold text-slate-500 ring-1 ring-slate-200">
<i class="fa-solid fa-minus"></i> Stabil vs kemarin
</span>
@endif
<div class="flex items-center gap-1 p-1 rounded-xl bg-gray-100">
<button type="button" @click="setPeriod(7)" :class="period === 7 ? 'bg-white shadow-sm text-blue-600' : 'text-gray-500 hover:text-gray-700'" class="px-3 py-1.5 text-xs font-bold rounded-lg transition">7 Hari</button>
<button type="button" @click="setPeriod(14)" :class="period === 14 ? 'bg-white shadow-sm text-blue-600' : 'text-gray-500 hover:text-gray-700'" class="px-3 py-1.5 text-xs font-bold rounded-lg transition">14 Hari</button>
<button type="button" @click="setPeriod(30)" :class="period === 30 ? 'bg-white shadow-sm text-blue-600' : 'text-gray-500 hover:text-gray-700'" class="px-3 py-1.5 text-xs font-bold rounded-lg transition">30 Hari</button>
</div>
</div>
</div>
<div class="p-5">
<div class="relative h-64">
<canvas id="trendChart"></canvas>
</div>
</div>
</div>
{{-- Grafik Jenis Pelanggaran --}}
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="flex flex-wrap items-center justify-between gap-3 px-5 py-4 border-b border-gray-100">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-orange-500 to-red-500 flex items-center justify-center text-white shadow-sm">
<i class="fa-solid fa-chart-column"></i>
</div>
<div>
<h3 class="text-sm font-bold text-gray-800">Pelanggaran per Jenis</h3>
<p class="text-[11px] text-gray-400">Jenis pelanggaran paling banyak</p>
</div>
</div>
<div class="flex items-center gap-1 rounded-xl bg-gray-100 p-1" x-data="{ typePeriod: 'today' }">
<button type="button" @click="typePeriod = 'today'; setTypePeriod('today')" class="rounded-lg px-3 py-1.5 text-[11px] font-bold transition"
:class="typePeriod === 'today' ? 'bg-white text-blue-600 shadow-sm' : 'text-gray-500 hover:text-gray-700'">Hari Ini</button>
<button type="button" @click="typePeriod = 'week'; setTypePeriod('week')" class="rounded-lg px-3 py-1.5 text-[11px] font-bold transition"
:class="typePeriod === 'week' ? 'bg-white text-blue-600 shadow-sm' : 'text-gray-500 hover:text-gray-700'">7 Hari</button>
<button type="button" @click="typePeriod = 'month'; setTypePeriod('month')" class="rounded-lg px-3 py-1.5 text-[11px] font-bold transition"
:class="typePeriod === 'month' ? 'bg-white text-blue-600 shadow-sm' : 'text-gray-500 hover:text-gray-700'">Bulan Ini</button>
</div>
</div>
<div class="p-5">
<div class="relative h-64">
<canvas id="typeChart"></canvas>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
{{-- Calendar --}}
<div class="lg:col-span-2 bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden"
@@ -340,5 +412,131 @@
};
}
</script>
<script src="/vendor/chartjs/chart.umd.min.js"></script>
<script>
function trendToggle() {
return {
period: 7,
total7: {{ $trendTotal7 }},
total14: {{ $trendTotal14 }},
total30: {{ $trendTotal30 }},
labels7: @json($dailyLabels7),
data7: @json($dailyData7),
labels14: @json($dailyLabels14),
data14: @json($dailyData14),
labels30: @json($dailyLabels30),
data30: @json($dailyData30),
chart: null,
init() {
const ctx = document.getElementById('trendChart');
if (!ctx || typeof Chart === 'undefined') return;
const g = ctx.getContext('2d');
const grad = g.createLinearGradient(0, 0, 0, 260);
grad.addColorStop(0, 'rgba(37, 99, 235, 0.28)');
grad.addColorStop(1, 'rgba(37, 99, 235, 0.02)');
this.chart = new Chart(ctx, {
type: 'line',
data: {
labels: this.labels7,
datasets: [
{
label: 'Pelanggaran',
data: this.data7,
borderColor: '#2563eb',
backgroundColor: grad,
fill: true,
tension: 0.4,
borderWidth: 2.5,
pointRadius: 4,
pointBackgroundColor: '#2563eb',
pointBorderColor: '#fff',
pointBorderWidth: 2,
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: '#0f172a',
padding: 10,
cornerRadius: 10,
titleFont: { weight: 'bold' },
callbacks: {
label: (ctx) => ctx.parsed.y + ' pelanggaran'
}
}
},
scales: {
y: {
beginAtZero: true,
ticks: { precision: 0, color: '#94a3b8' },
grid: { color: '#f1f5f9' }
},
x: {
ticks: { color: '#94a3b8', maxRotation: 0, autoSkip: true, maxTicksLimit: 15 },
grid: { display: false }
}
}
}
});
},
setPeriod(n) {
this.period = n;
if (!this.chart) return;
this.chart.data.labels = n === 7 ? this.labels7 : (n === 14 ? this.labels14 : this.labels30);
this.chart.data.datasets[0].data = n === 7 ? this.data7 : (n === 14 ? this.data14 : this.data30);
this.chart.update();
},
};
}
{{-- Grafik batang: pelanggaran per jenis --}}
const typeCtx = document.getElementById('typeChart');
let typeChart = null;
if (typeCtx && typeof Chart !== 'undefined') {
typeChart = new Chart(typeCtx, {
type: 'bar',
data: {
labels: @json($typeNames),
datasets: [
{ label: 'Hari Ini', data: @json($typeToday), backgroundColor: 'rgba(249, 115, 22, 0.85)', borderRadius: 8, barThickness: 18 },
{ label: '7 Hari', data: @json($typeWeek), backgroundColor: 'rgba(59, 130, 246, 0.85)', borderRadius: 8, barThickness: 18, hidden: true },
{ label: 'Bulan Ini', data: @json($typeMonth), backgroundColor: 'rgba(139, 92, 246, 0.85)', borderRadius: 8, barThickness: 18, hidden: true }
]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: '#0f172a',
callbacks: { label: (ctx) => ctx.parsed.x + ' pelanggaran' }
}
},
scales: {
x: { beginAtZero: true, ticks: { precision: 0, color: '#94a3b8' }, grid: { color: '#f1f5f9' } },
y: { ticks: { color: '#64748b', font: { size: 11 } }, grid: { display: false } }
}
}
});
}
window.setTypePeriod = function (p) {
if (!typeChart) return;
const map = { today: 0, week: 1, month: 2 };
Object.keys(map).forEach(k => {
typeChart.data.datasets[map[k]].hidden = k !== p;
});
typeChart.update();
};
</script>
@endpush
@endsection