MIF_E31230680/resources/views/admin/dashboard.blade.php

509 lines
22 KiB
PHP

@extends('layout.app')
@section('content')
{{-- Tambahan CSS Khusus untuk Mempercantik Tampilan --}}
<style>
.card-custom {
border: none;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
.card-custom:hover {
transform: translateY(-3px);
box-shadow: 0 8px 15px rgba(0,0,0,0.08);
}
.info-stats4 {
border: none !important;
border-radius: 12px;
background: #ffffff;
box-shadow: 0 4px 12px rgba(0,0,0,0.03);
transition: transform 0.2s;
}
.info-stats4:hover {
transform: translateY(-2px);
}
.table-modern td, .table-modern th {
vertical-align: middle !important;
border-top: none !important;
border-bottom: 1px solid #f1f3f5;
padding: 1rem 0.75rem;
}
.table-modern thead th {
color: #6c757d;
font-weight: 600;
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 0.5px;
border-bottom: 2px solid #e9ecef !important;
}
.badge-rank {
font-size: 0.85rem;
padding: 0.4em 0.8em;
border-radius: 20px;
}
.progress-pill {
border-radius: 10px;
}
</style>
<div class="page-header">
<ol class="breadcrumb">
<li class="breadcrumb-item">Home</li>
<li class="breadcrumb-item active text-primary font-weight-bold">Dashboard</li>
</ol>
{{-- Filter Dropdown Periode --}}
<div class="ml-auto">
<form action="{{ url()->current() }}" method="GET" class="form-inline m-0">
<div class="input-group shadow-sm" style="border-radius: 8px; overflow: hidden;">
<div class="input-group-prepend">
<span class="input-group-text bg-primary text-white border-0">
<i class="icon-calendar"></i>
</span>
</div>
<select name="periode_id" id="periode_id" class="form-control border-0 bg-white" onchange="this.form.submit()" style="cursor: pointer; font-weight: 500;">
@forelse($semuaPeriode as $p)
<option value="{{ $p->id }}" {{ ($periodeDitampilkan && $periodeDitampilkan->id == $p->id) ? 'selected' : '' }}>
{{ $p->nama_periode }} {{ $p->is_aktif ? '(Aktif Saat Ini)' : '' }}
</option>
@empty
<option value="">Belum Ada Periode</option>
@endforelse
</select>
</div>
</form>
</div>
</div>
{{-- BAGIAN CARD YANG SUDAH DIBUAT LEBIH SLIM --}}
<div class="row gutters mb-3">
@php
// Helper array untuk merapikan perulangan card 1-3
$cards = [
['icon' => 'icon-user1', 'color' => 'primary', 'val' => $countUser, 'lab' => 'Total Pengguna'],
['icon' => 'icon-users', 'color' => 'info', 'val' => $countWarga, 'lab' => 'Total Warga'],
['icon' => 'icon-verified_user', 'color' => 'success', 'val' => $countVerif, 'lab' => 'Dokumen Valid'],
];
@endphp
@foreach($cards as $c)
<div class="col-xl-3 col-lg-6 col-md-6 col-sm-6 col-12 mb-3">
<div class="info-stats4 p-3 h-100 d-flex flex-column justify-content-center">
<div class="d-flex align-items-center">
<div class="info-icon bg-light text-{{ $c['color'] }} rounded-circle m-0 mr-3" style="width: 40px; height: 40px; line-height: 40px; text-align: center; font-size: 1.2rem;">
<i class="{{ $c['icon'] }}"></i>
</div>
<div class="sale-num">
<h4 class="mb-0 font-weight-bold" style="font-size: 1.2rem;">{{ $c['val'] }}</h4>
<p class="text-muted m-0" style="font-size: 0.8rem;">{{ $c['lab'] }}</p>
</div>
</div>
</div>
</div>
@endforeach
{{-- Card 4: Indikator Kuota --}}
<div class="col-xl-3 col-lg-6 col-md-6 col-sm-6 col-12 mb-3">
<div class="info-stats4 p-3 h-100 d-flex flex-column">
<div class="d-flex justify-content-between align-items-center">
<div class="info-icon bg-light text-warning rounded-circle m-0" style="width: 40px; height: 40px; line-height: 40px; text-align: center; font-size: 1.2rem;">
<i class="icon-pie-chart1"></i>
</div>
<div class="text-right">
<h4 class="m-0 font-weight-bold" style="font-size: 1.2rem;">{{ $chartUtama }} <span class="text-muted" style="font-size: 0.8rem;">/ {{ $kuota }}</span></h4>
</div>
</div>
<div class="mt-2">
<div class="progress progress-pill bg-light" style="height: 6px;">
<div class="progress-bar progress-pill {{ $persentaseKuota >= 100 ? 'bg-danger' : 'bg-primary' }}"
role="progressbar"
style="width: {{ $persentaseKuota }}%"
aria-valuenow="{{ $persentaseKuota }}" aria-valuemin="0" aria-valuemax="100">
</div>
</div>
<div class="d-flex justify-content-between mt-1">
<small class="text-muted" style="font-size: 0.7rem;">Terisi ({{ $namaPeriode }})</small>
<small class="text-muted font-weight-bold" style="font-size: 0.7rem;">{{ $persentaseKuota }}%</small>
</div>
</div>
</div>
</div>
</div>
<div class="row gutters mt-3">
<div class="col-xl-5 col-lg-12 col-md-12 col-sm-12 col-12 mb-3">
<div class="card card-custom h-100">
<div class="card-header bg-white pt-4 pb-2 border-0">
<h6 class="card-title mb-0 font-weight-bold text-dark">Proporsi Verifikasi Dokumen</h6>
</div>
<div class="card-body">
<div style="height: 280px; width: 100%; position: relative;">
<canvas id="verifikasiPieChart"></canvas>
</div>
</div>
</div>
</div>
<div class="col-xl-7 col-lg-12 col-md-12 col-sm-12 col-12 mb-3">
<div class="card card-custom h-100">
<div class="card-header bg-white pt-4 pb-2 border-0 d-flex justify-content-between align-items-center">
<h6 class="card-title mb-0 font-weight-bold text-dark">Hasil Seleksi: {{ $namaPeriode }}</h6>
<span class="badge badge-pill badge-info px-3 py-2 text-white shadow-sm">Berdasarkan Kuota</span>
</div>
<div class="card-body">
@if($countRanking == 0)
<div class="text-center d-flex flex-column justify-content-center align-items-center bg-light rounded" style="height: 280px; border: 1px dashed #ced4da;">
<i class="icon-inbox text-muted mb-3" style="font-size: 3rem;"></i>
<p class="text-muted mb-0">Belum ada data seleksi untuk periode ini.<br>Silakan lakukan proses perangkingan.</p>
</div>
@else
<div style="height: 280px; width: 100%; position: relative;">
<canvas id="peringkatBarChart"></canvas>
</div>
@endif
</div>
</div>
</div>
</div>
<div class="row gutters mt-3">
{{-- Tabel Top 5 --}}
<div class="col-xl-8 col-lg-12 col-md-12 col-sm-12 col-12 mb-3">
<div class="card card-custom h-100">
<div class="card-header bg-white pt-4 pb-3 border-bottom d-flex justify-content-between align-items-center">
<h6 class="card-title mb-0 font-weight-bold text-dark">
<i class="icon-award text-warning mr-2"></i>Top 5 Prioritas Utama
</h6>
<a href="{{ route('seleksi.index') }}" class="btn btn-sm btn-light text-primary font-weight-bold border shadow-sm px-3 rounded-pill">Lihat Detail Lengkap</a>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover table-modern m-0">
<thead class="bg-light">
<tr>
<th class="pl-4">Rank</th>
<th>Profil Warga</th>
<th>Alamat Terdaftar</th>
<th class="text-center">Skor Kelayakan</th>
</tr>
</thead>
<tbody>
@forelse($topWarga as $top)
<tr>
<td class="pl-4">
<span class="badge badge-warning badge-rank text-dark shadow-sm">
<i class="icon-star mr-1"></i>{{ $top->ranking }}
</span>
</td>
<td>
<strong class="d-block text-dark" style="font-size: 0.95rem;">{{ $top->warga->nama }}</strong>
<small class="text-muted"><i class="icon-credit-card mr-1"></i>{{ $top->warga->nik }}</small>
</td>
<td>
<span class="text-dark">{{ $top->warga->alamat }}</span><br>
<small class="text-muted">RT {{ $top->warga->rt }} / RW {{ $top->warga->rw }}</small>
</td>
<td class="text-center">
<span class="font-weight-bold text-success" style="font-size: 1.1rem;">{{ number_format($top->skor_akhir, 4) }}</span>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="text-center py-5 text-muted">Belum ada warga yang diseleksi.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
{{-- Notifikasi --}}
<div class="col-xl-4 col-lg-12 col-md-12 col-sm-12 col-12 mb-3">
<div class="card card-custom h-100">
<div class="card-header bg-white pt-4 pb-2 border-0">
<h6 class="card-title mb-0 font-weight-bold text-dark"><i class="icon-bell text-danger mr-2"></i>Perhatian Khusus</h6>
</div>
<div class="card-body p-3">
<ul class="list-group list-group-flush border-0">
@if($countPending > 0)
<li class="list-group-item d-flex justify-content-between align-items-center rounded bg-light mb-2 border-0">
<div>
<i class="icon-alert-circle text-warning mr-2"></i>
<span class="text-dark font-weight-500">Verifikasi Tertunda</span>
</div>
<span class="badge badge-warning badge-pill shadow-sm px-2 py-1">{{ $countPending }}</span>
</li>
@endif
@if($persentaseKuota >= 100)
<li class="list-group-item d-flex justify-content-between align-items-center rounded bg-light mb-2 border-0">
<div>
<i class="icon-info text-info mr-2"></i>
<span class="text-dark font-weight-500">Kuota Terisi Penuh</span>
</div>
<i class="icon-check-circle text-success"></i>
</li>
@endif
@if($periodeDitampilkan && $periodeDitampilkan->is_aktif && $countRanking == 0)
<li class="list-group-item d-flex flex-column align-items-center justify-content-center rounded bg-light p-4 text-center border-0 mt-2">
<i class="icon-activity text-danger mb-2" style="font-size: 2rem;"></i>
<span class="text-dark font-weight-500 mb-3">Belum ada proses perhitungan.</span>
<a href="{{ route('seleksi.index') }}" class="btn btn-danger btn-sm shadow-sm rounded-pill px-4">Proses Seleksi Sekarang</a>
</li>
@endif
@if($countPending == 0 && ($countRanking > 0 || !$periodeDitampilkan?->is_aktif) && $persentaseKuota < 100)
<li class="list-group-item text-center text-muted border-0 py-5">
<div class="bg-light rounded-circle mx-auto d-flex justify-content-center align-items-center" style="width: 80px; height: 80px;">
<i class="icon-check text-success" style="font-size: 2.5rem;"></i>
</div>
<h6 class="mt-4 font-weight-bold text-dark">Sistem Aman</h6>
<span class="d-block text-muted" style="font-size: 0.9rem;">Tidak ada peringatan mendesak saat ini.</span>
</li>
@endif
</ul>
</div>
</div>
</div>
</div>
{{-- TAMBAHAN BARU: Baris untuk Grafik Tren (Line Chart) --}}
<div class="row gutters mt-3">
<div class="col-xl-12 col-lg-12 col-md-12 col-sm-12 col-12 mb-3">
<div class="card card-custom h-100">
<div class="card-header bg-white pt-4 pb-2 border-0">
<h6 class="card-title mb-0 font-weight-bold text-dark">
<i class="icon-trending-up text-primary mr-2"></i>Tren Penerima Prioritas Utama Lintas Periode
</h6>
</div>
<div class="card-body">
@if(count($labelTren) < 1)
<div class="text-center py-5 text-muted bg-light rounded" style="border: 1px dashed #ced4da;">
Belum ada data periode untuk ditampilkan trennya.
</div>
@else
<div style="height: 300px; width: 100%; position: relative;">
<canvas id="trendLineChart"></canvas>
</div>
@endif
</div>
</div>
</div>
</div>
{{-- TAMBAHAN BARU 2: Baris untuk Grafik Demografi Kriteria --}}
<div class="row gutters mt-3">
<div class="col-xl-12 col-lg-12 col-md-12 col-sm-12 col-12 mb-3">
<div class="card card-custom h-100">
<div class="card-header bg-white pt-4 pb-2 border-0 d-flex justify-content-between align-items-center">
<h6 class="card-title mb-0 font-weight-bold text-dark">
<i class="icon-pie-chart text-success mr-2"></i>Demografi Warga
</h6>
{{-- Dropdown Pilih Kriteria --}}
<form action="{{ url()->current() }}" method="GET" class="form-inline m-0">
{{-- Pertahankan filter periode_id agar tidak hilang saat ganti chart --}}
<input type="hidden" name="periode_id" value="{{ request('periode_id') }}">
<select name="kriteria_chart" class="form-control form-control-sm border bg-light shadow-sm" onchange="this.form.submit()" style="border-radius: 20px; cursor: pointer;">
@foreach($semuaKriteria as $k)
<option value="{{ $k->id }}" {{ ($kriteriaDitampilkan && $kriteriaDitampilkan->id == $k->id) ? 'selected' : '' }}>
Berdasarkan {{ $k->nama ?? 'Kriteria' }}
</option>
@endforeach
</select>
</form>
</div>
<div class="card-body">
@if($statistikKriteria->isEmpty())
<div class="text-center py-5 text-muted bg-light rounded" style="border: 1px dashed #ced4da;">
Belum ada data detail warga untuk kriteria ini.
</div>
@else
<div style="height: 300px; width: 100%; position: relative;">
<canvas id="demografiChart"></canvas>
</div>
@endif
</div>
</div>
</div>
</div>
{{-- tutup dulu --}}
@endsection
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// Chart 1: Doughnut Chart
const ctx1 = document.getElementById('verifikasiPieChart').getContext('2d');
new Chart(ctx1, {
type: 'doughnut',
data: {
labels: ['Terverifikasi', 'Menunggu', 'Ditolak'],
datasets: [{
data: [{{ $countVerif }}, {{ $countPending }}, {{ $countTolak }}],
backgroundColor: [
'rgba(40, 167, 69, 0.85)',
'rgba(255, 193, 7, 0.85)',
'rgba(220, 53, 69, 0.85)'
],
borderWidth: 0,
hoverOffset: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '70%',
plugins: {
legend: {
position: 'bottom',
labels: { padding: 20, usePointStyle: true, pointStyle: 'circle' }
}
}
}
});
// Chart 2: Bar Chart
@if($countRanking > 0)
const ctx2 = document.getElementById('peringkatBarChart').getContext('2d');
new Chart(ctx2, {
type: 'bar',
data: {
labels: ['Utama', 'Cadangan', 'Tidak Layak', 'Dialihkan'],
datasets: [{
label: 'Jumlah Warga',
data: [{{ $chartUtama }}, {{ $chartCadangan }}, {{ $chartTidakLayak }}, {{ $chartDialihkan }}],
backgroundColor: [
'rgba(0, 123, 255, 0.85)',
'rgba(23, 162, 184, 0.85)',
'rgba(108, 117, 125, 0.85)',
'rgba(111, 66, 193, 0.85)'
],
borderWidth: 0,
borderRadius: 8,
barPercentage: 0.6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
grid: { display: false }
},
y: {
beginAtZero: true,
border: { display: false },
grid: {
color: '#f1f3f5'
}
}
},
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: 12,
titleFont: { size: 14 },
bodyFont: { size: 14 }
}
}
}
});
@endif
// Chart 3: Trend Line Chart (Grafik Garis Perbandingan Periode)
@if(count($labelTren) > 0)
const ctx3 = document.getElementById('trendLineChart').getContext('2d');
new Chart(ctx3, {
type: 'line',
data: {
// Label dari Controller (Misal: ['Maret 2026', 'April 2026', 'Mei 2026'])
labels: {!! json_encode($labelTren) !!},
datasets: [{
label: 'Penerima Prioritas Utama',
data: {!! json_encode($dataUtamaTren) !!},
borderColor: 'rgba(0, 123, 255, 1)',
backgroundColor: 'rgba(0, 123, 255, 0.1)', // Efek warna tipis di bawah garis
borderWidth: 3,
pointBackgroundColor: '#ffffff',
pointBorderColor: 'rgba(0, 123, 255, 1)',
pointBorderWidth: 2,
pointRadius: 5,
pointHoverRadius: 7,
fill: true, // Menyalakan efek background bawah garis
tension: 0.4 // Membuat garisnya melengkung halus (tidak kaku)
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
grid: { display: false }
},
y: {
beginAtZero: true,
border: { display: false },
grid: { color: '#f1f3f5' }
}
},
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.8)',
padding: 12,
titleFont: { size: 14 },
bodyFont: { size: 14 },
displayColors: false
}
}
}
});
@endif
// grafik demografi kriteria
// Chart 4: Demografi Chart (Berdasarkan Dropdown)
@if($statistikKriteria->isNotEmpty())
const ctx4 = document.getElementById('demografiChart').getContext('2d');
new Chart(ctx4, {
type: 'bar', // Kamu bisa ubah jadi 'doughnut' kalau mau bentuk lingkaran
data: {
labels: {!! json_encode($statistikKriteria->keys()) !!},
datasets: [{
label: 'Jumlah Warga',
data: {!! json_encode($statistikKriteria->values()) !!},
backgroundColor: [
'rgba(23, 162, 184, 0.85)',
'rgba(255, 193, 7, 0.85)',
'rgba(40, 167, 69, 0.85)',
'rgba(220, 53, 69, 0.85)',
'rgba(111, 66, 193, 0.85)',
'rgba(0, 123, 255, 0.85)'
],
borderWidth: 0,
borderRadius: 6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false } // Ubah ke true jika kamu mengganti type chart menjadi 'doughnut'
},
scales: {
x: { grid: { display: false } },
y: { beginAtZero: true, grid: { color: '#f1f3f5' } }
}
}
});
@endif
</script>
@endpush