refine dashboard and admin access

This commit is contained in:
micko samawa 2026-06-14 21:15:56 +07:00
parent 721c5dd9d4
commit 3e6188bfaa
11 changed files with 235 additions and 78 deletions

View File

@ -26,13 +26,11 @@ public function index(Request $request)
// ========== FILTER: ADMIN (Hanya booking dari kontrakan milik admin) ==========
$admin = auth()->guard('admin')->user();
if ($admin) {
if ($admin->role !== 'super_admin') {
// Admin biasa hanya bisa lihat booking dari kontrakan milik mereka
$query->whereHas('kontrakan', function($q) use ($admin) {
$q->where('admin_id', $admin->id);
});
}
// Super admin bisa lihat semua booking
// Semua admin, termasuk super admin, hanya melihat booking
// dari kontrakan yang mereka input sendiri.
$query->whereHas('kontrakan', function($q) use ($admin) {
$q->where('admin_id', $admin->id);
});
}
// Filter by status
@ -50,7 +48,7 @@ public function index(Request $request)
// ========== GET KONTRAKANS (sesuai filter admin) ==========
$kontrakanQuery = Kontrakan::orderBy('nama');
if ($admin && $admin->role !== 'super_admin') {
if ($admin) {
$kontrakanQuery->where('admin_id', $admin->id);
}
@ -68,7 +66,7 @@ public function create(Request $request)
// ========== FILTER: ADMIN (Hanya kontrakan milik admin) ==========
$admin = auth()->guard('admin')->user();
if ($admin && $admin->role !== 'super_admin') {
if ($admin) {
$kontrakanQuery->where('admin_id', $admin->id);
}
@ -113,7 +111,7 @@ public function store(Request $request)
// ========== AUTHORIZATION: Cek apakah admin punya akses ke kontrakan ini ==========
$admin = auth()->guard('admin')->user();
if ($admin && $admin->role !== 'super_admin') {
if ($admin) {
if ($kontrakan->admin_id !== $admin->id) {
throw new Exception('Anda tidak memiliki akses ke kontrakan ini.');
}
@ -238,7 +236,7 @@ public function show(Booking $booking)
{
// ========== AUTHORIZATION: Cek apakah admin punya akses ke booking ini ==========
$admin = auth()->guard('admin')->user();
if ($admin && $admin->role !== 'super_admin') {
if ($admin) {
// Admin biasa hanya bisa lihat booking dari kontrakan milik mereka
if ($booking->kontrakan->admin_id !== $admin->id) {
abort(403, 'Anda tidak memiliki akses ke booking ini.');
@ -257,7 +255,7 @@ public function paymentProof(Booking $booking)
{
// ========== AUTHORIZATION: Cek apakah admin punya akses ke booking ini ==========
$admin = auth()->guard('admin')->user();
if ($admin && $admin->role !== 'super_admin') {
if ($admin) {
if ($booking->kontrakan->admin_id !== $admin->id) {
abort(403, 'Anda tidak memiliki akses ke booking ini.');
}
@ -309,7 +307,7 @@ public function edit(Booking $booking)
{
// ========== AUTHORIZATION: Cek apakah admin punya akses ke booking ini ==========
$admin = auth()->guard('admin')->user();
if ($admin && $admin->role !== 'super_admin') {
if ($admin) {
// Admin biasa hanya bisa edit booking dari kontrakan milik mereka
if ($booking->kontrakan->admin_id !== $admin->id) {
abort(403, 'Anda tidak memiliki akses ke booking ini.');
@ -322,7 +320,7 @@ public function edit(Booking $booking)
$kontrakanQuery = Kontrakan::orderBy('nama');
if ($admin && $admin->role !== 'super_admin') {
if ($admin) {
$kontrakanQuery->where('admin_id', $admin->id);
}
@ -337,7 +335,7 @@ public function update(Request $request, Booking $booking)
{
// ========== AUTHORIZATION: Cek apakah admin punya akses ke booking ini ==========
$admin = auth()->guard('admin')->user();
if ($admin && $admin->role !== 'super_admin') {
if ($admin) {
if ($booking->kontrakan->admin_id !== $admin->id) {
abort(403, 'Anda tidak memiliki akses ke booking ini.');
}
@ -401,7 +399,7 @@ public function confirm(Booking $booking)
{
// ========== AUTHORIZATION ==========
$admin = auth()->guard('admin')->user();
if ($admin && $admin->role !== 'super_admin') {
if ($admin) {
if ($booking->kontrakan->admin_id !== $admin->id) {
abort(403, 'Anda tidak memiliki akses ke booking ini.');
}
@ -421,7 +419,7 @@ public function checkIn(Booking $booking)
{
// ========== AUTHORIZATION ==========
$admin = auth()->guard('admin')->user();
if ($admin && $admin->role !== 'super_admin') {
if ($admin) {
if ($booking->kontrakan->admin_id !== $admin->id) {
abort(403, 'Anda tidak memiliki akses ke booking ini.');
}
@ -441,7 +439,7 @@ public function checkOut(Booking $booking)
{
// ========== AUTHORIZATION ==========
$admin = auth()->guard('admin')->user();
if ($admin && $admin->role !== 'super_admin') {
if ($admin) {
if ($booking->kontrakan->admin_id !== $admin->id) {
abort(403, 'Anda tidak memiliki akses ke booking ini.');
}
@ -461,7 +459,7 @@ public function cancel(Request $request, Booking $booking)
{
// ========== AUTHORIZATION ==========
$admin = auth()->guard('admin')->user();
if ($admin && $admin->role !== 'super_admin') {
if ($admin) {
if ($booking->kontrakan->admin_id !== $admin->id) {
abort(403, 'Anda tidak memiliki akses ke booking ini.');
}
@ -483,7 +481,7 @@ public function markPaid(Request $request, Booking $booking)
{
// ========== AUTHORIZATION ==========
$admin = auth()->guard('admin')->user();
if ($admin && $admin->role !== 'super_admin') {
if ($admin) {
if ($booking->kontrakan->admin_id !== $admin->id) {
abort(403, 'Anda tidak memiliki akses ke booking ini.');
}
@ -505,7 +503,7 @@ public function togglePaymentStatus(Request $request, Booking $booking)
{
// ========== AUTHORIZATION ==========
$admin = auth()->guard('admin')->user();
if ($admin && $admin->role !== 'super_admin') {
if ($admin) {
if ($booking->kontrakan->admin_id !== $admin->id) {
abort(403, 'Anda tidak memiliki akses ke booking ini.');
}
@ -537,7 +535,7 @@ public function destroy(Booking $booking)
{
// ========== AUTHORIZATION ==========
$admin = auth()->guard('admin')->user();
if ($admin && $admin->role !== 'super_admin') {
if ($admin) {
if ($booking->kontrakan->admin_id !== $admin->id) {
abort(403, 'Anda tidak memiliki akses ke booking ini.');
}
@ -566,7 +564,7 @@ public function bulkDestroy(Request $request)
// ========== AUTHORIZATION: Filter berdasarkan admin ==========
$admin = auth()->guard('admin')->user();
if ($admin && $admin->role !== 'super_admin') {
if ($admin) {
// Admin biasa hanya bisa hapus booking dari kontrakan milik mereka
$query->whereHas('kontrakan', function($q) use ($admin) {
$q->where('admin_id', $admin->id);

View File

@ -27,14 +27,14 @@ public function index()
// ========== CACHE STATISTIK (5 menit) ==========
$stats = Cache::remember('dashboard_stats_admin_' . $adminId, 300, function () use ($adminId, $isSuperAdmin) {
return [
'jumlahKontrakan' => Kontrakan::where('admin_id', $adminId)->count(),
'jumlahKontrakan' => $isSuperAdmin ? Kontrakan::count() : Kontrakan::where('admin_id', $adminId)->count(),
'jumlahLaundry' => $isSuperAdmin ? Laundry::count() : 0,
'jumlahKriteria' => $isSuperAdmin ? Kriteria::count() : 0,
];
});
// ========== DATA TERBARU (No Cache) ==========
$recentKontrakan = Kontrakan::where('admin_id', $adminId)
$recentKontrakan = ($isSuperAdmin ? Kontrakan::query() : Kontrakan::where('admin_id', $adminId))
->latest()
->select('id', 'nama', 'alamat', 'harga', 'jarak', 'jumlah_kamar', 'created_at') // Only needed columns
->take(5)
@ -54,7 +54,7 @@ public function index()
$chartData = Cache::remember('dashboard_charts_admin_' . $adminId, 600, function () use ($adminId, $isSuperAdmin) {
// 1. Harga Kontrakan (Top 5)
$hargaKontrakan = Kontrakan::where('admin_id', $adminId)->select('nama', 'harga')
$hargaKontrakan = ($isSuperAdmin ? Kontrakan::query() : Kontrakan::where('admin_id', $adminId))->select('nama', 'harga')
->orderBy('harga', 'desc')
->take(5)
->get();
@ -73,7 +73,7 @@ public function index()
// 3. Distribusi Jarak (Single Query dengan CASE)
$jarakKontrakan = DB::table('kontrakans')
->where('admin_id', $adminId)
->when(!$isSuperAdmin, function ($q) use ($adminId) { return $q->where('admin_id', $adminId); })
->selectRaw("
SUM(CASE WHEN jarak <= 500 THEN 1 ELSE 0 END) as dekat,
SUM(CASE WHEN jarak > 500 AND jarak <= 1000 THEN 1 ELSE 0 END) as sedang,
@ -91,7 +91,7 @@ public function index()
// 4. Statistik Aggregate (Single Query)
$kontrakanStats = DB::table('kontrakans')
->where('admin_id', $adminId)
->when(!$isSuperAdmin, function ($q) use ($adminId) { return $q->where('admin_id', $adminId); })
->selectRaw('
AVG(harga) as avg_harga,
AVG(jarak) as avg_jarak,
@ -102,7 +102,7 @@ public function index()
->first();
// 5. Top Kontrakan by Harga
$topKontrakan = Kontrakan::where('admin_id', $adminId)->select('nama', 'harga', 'jumlah_kamar', 'jarak')
$topKontrakan = ($isSuperAdmin ? Kontrakan::query() : Kontrakan::where('admin_id', $adminId))->select('nama', 'harga', 'jumlah_kamar', 'jarak')
->orderBy('harga', 'desc')
->take(5)
->get();
@ -155,7 +155,7 @@ public function index()
// ========== INSIGHT REAL-TIME (No Cache) ==========
$realtimeJarakKontrakan = DB::table('kontrakans')
->where('admin_id', $adminId)
->when(!$isSuperAdmin, function ($q) use ($adminId) { return $q->where('admin_id', $adminId); })
->selectRaw("
SUM(CASE WHEN jarak <= 500 THEN 1 ELSE 0 END) as dekat,
SUM(CASE WHEN jarak > 500 AND jarak <= 1000 THEN 1 ELSE 0 END) as sedang,
@ -164,7 +164,7 @@ public function index()
->first();
$realtimeKontrakanStats = DB::table('kontrakans')
->where('admin_id', $adminId)
->when(!$isSuperAdmin, function ($q) use ($adminId) { return $q->where('admin_id', $adminId); })
->selectRaw('
AVG(harga) as avg_harga,
AVG(jarak) as avg_jarak,

View File

@ -114,24 +114,31 @@ public function index(Request $request)
// ========== STATISTIK & DATA UNTUK FILTER UI ==========
// Total kontrakan (semua data, tanpa filter)
$totalKontrakan = Kontrakan::count();
// Statistik hanya berdasarkan kontrakan milik admin yang sedang login
$statQuery = Kontrakan::query();
if ($admin && $admin->role !== 'super_admin') {
$statQuery->where('admin_id', $admin->id);
}
$totalKontrakan = $statQuery->count();
// Jumlah hasil setelah filter
$filteredCount = $kontrakan->total();
// Range harga untuk slider (min & max dari database)
$hasData = Kontrakan::count() > 0;
$hargaMin = $hasData ? (Kontrakan::min('harga') ?? 0) : 0;
$hargaMax = $hasData ? (Kontrakan::max('harga') ?? 0) : 10000000;
// Range jarak untuk slider (dalam km)
$jarakMaxDb = $hasData ? (Kontrakan::max('jarak') ?? 0) : 0; // dalam meter
$jarakMaxKm = $jarakMaxDb > 0 ? ceil($jarakMaxDb / 1000) : 0; // konversi ke km, bulatkan ke atas
// Range jumlah_kamar untuk input
$jumlah_kamarMin = $hasData ? (Kontrakan::min('jumlah_kamar') ?? 0) : 0;
$jumlah_kamarMax = $hasData ? (Kontrakan::max('jumlah_kamar') ?? 0) : 0;
$hasData = $totalKontrakan > 0;
// Range harga untuk slider/statistik
$hargaMin = $hasData ? ($statQuery->min('harga') ?? 0) : 0;
$hargaMax = $hasData ? ($statQuery->max('harga') ?? 0) : 0;
// Range jarak untuk slider/statistik
$jarakMaxDb = $hasData ? ($statQuery->max('jarak') ?? 0) : 0;
$jarakMaxKm = $jarakMaxDb > 0 ? ceil($jarakMaxDb / 1000) : 0;
// Range jumlah kamar untuk statistik
$jumlah_kamarMin = $hasData ? ($statQuery->min('jumlah_kamar') ?? 0) : 0;
$jumlah_kamarMax = $hasData ? ($statQuery->max('jumlah_kamar') ?? 0) : 0;
// Daftar fasilitas unik untuk checkbox (extract dari semua kontrakan)
// Daftar fasilitas unik untuk checkbox (extract dari semua kontrakan)

View File

@ -1,6 +1,6 @@
@extends('layouts.admin')
@section('title', 'Tambah Kriteria')
@section('title', 'Tambah Master Kriteria')
@section('content')
<div class="container-fluid px-4">
@ -79,7 +79,7 @@
<nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ route('dashboard') }}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{{ route('kriteria.index') }}">Kriteria</a></li>
<li class="breadcrumb-item"><a href="{{ route('kriteria.index') }}">Master Kriteria SAW</a></li>
<li class="breadcrumb-item active">Tambah Data</li>
</ol>
</nav>
@ -87,7 +87,7 @@
<!-- Page Header -->
<div class="page-header mb-4">
<h2 class="mb-2">
<i class="bi bi-plus-circle me-2"></i>Tambah Kriteria SAW
<i class="bi bi-plus-circle me-2"></i>Tambah Master Kriteria SAW
</h2>
<p class="mb-0 fs-6">Lengkapi formulir untuk menambahkan kriteria penilaian baru</p>
</div>
@ -165,7 +165,7 @@ class="form-control border-start-0 @error('nama_kriteria') is-invalid @enderror"
<!-- Bobot -->
<div class="col-md-6">
<label for="bobot" class="form-label fw-semibold">
Bobot Kriteria <span class="text-danger">*</span>
Bobot Default / Simulasi <span class="text-danger">*</span>
</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0">
@ -187,7 +187,7 @@ class="form-control border-start-0 @error('bobot') is-invalid @enderror"
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<small class="text-muted">Nilai bobot antara 0 - 1 (contoh: 0.25 atau 0.3)</small>
<small class="text-muted">Bobot ini hanya digunakan sebagai bobot default/simulasi untuk validasi perhitungan di web. Bobot utama rekomendasi tetap dipilih oleh user melalui aplikasi mobile.</small>
</div>
<!-- Tipe -->

View File

@ -1,6 +1,6 @@
@extends('layouts.admin')
@section('title', 'Edit Kriteria')
@section('title', 'Edit Master Kriteria')
@section('content')
<div class="container-fluid px-4">
@ -79,7 +79,7 @@
<nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ route('dashboard') }}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{{ route('kriteria.index') }}">Kriteria</a></li>
<li class="breadcrumb-item"><a href="{{ route('kriteria.index') }}">Master Kriteria SAW</a></li>
<li class="breadcrumb-item active">Edit Data</li>
</ol>
</nav>
@ -87,7 +87,7 @@
<!-- Page Header -->
<div class="page-header mb-4">
<h2 class="mb-2">
<i class="bi bi-pencil-square me-2"></i>Edit Kriteria SAW
<i class="bi bi-pencil-square me-2"></i>Edit Master Kriteria SAW
</h2>
<p class="mb-0 fs-6">Update informasi kriteria <strong>{{ $kriteria->nama_kriteria }}</strong></p>
</div>
@ -166,7 +166,7 @@ class="form-control border-start-0 @error('nama_kriteria') is-invalid @enderror"
<!-- Bobot -->
<div class="col-md-6">
<label for="bobot" class="form-label fw-semibold">
Bobot Kriteria <span class="text-danger">*</span>
Bobot Default / Simulasi <span class="text-danger">*</span>
</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0">
@ -188,7 +188,7 @@ class="form-control border-start-0 @error('bobot') is-invalid @enderror"
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<small class="text-muted">Nilai bobot antara 0 - 1 (contoh: 0.25 atau 0.3)</small>
<small class="text-muted">Bobot ini hanya digunakan sebagai bobot default/simulasi untuk validasi perhitungan di web. Bobot utama rekomendasi tetap dipilih oleh user melalui aplikasi mobile.</small>
</div>
<!-- Tipe -->

View File

@ -1,6 +1,6 @@
@extends('layouts.admin')
@section('title', 'Kelola Kriteria SAW')
@section('title', 'Master Kriteria SAW')
@section('content')
<div class="container-fluid px-2 px-md-4 page-kriteria">
@ -309,7 +309,7 @@
<div class="col-12 col-md-8">
<h2 class="mb-2 fw-bold">
<i class="bi bi-diagram-3 me-2 me-md-3"></i>
<span class="d-none d-sm-inline">📊 Kelola Kriteria SAW</span>
<span class="d-none d-sm-inline">📊 Master Kriteria SAW</span>
<span class="d-sm-none">📊 Kriteria</span>
</h2>
<p class="mb-0 opacity-90">
@ -320,7 +320,7 @@
<div class="col-12 col-md-4 text-md-end mt-3 mt-md-0">
<a href="{{ route('kriteria.create') }}" class="btn btn-kriteria">
<i class="bi bi-plus-circle me-2"></i>
<span class="d-none d-sm-inline">Tambah Kriteria</span>
<span class="d-none d-sm-inline">Tambah Master Kriteria</span>
<span class="d-sm-none">Tambah</span>
</a>
</div>
@ -444,7 +444,7 @@ class="form-control border-start-0"
<h5 class="mb-3" style="color: var(--text-secondary); font-weight: 600;">Belum ada kriteria</h5>
<p class="text-muted mb-4 px-md-5">Mulai tambahkan kriteria untuk penilaian SAW (Simple Additive Weighting) agar dapat melakukan perhitungan rekomendasi yang akurat.</p>
<a href="{{ route('kriteria.create') }}" class="btn btn-primary btn-lg rounded-3 px-4" style="box-shadow: 0 6px 20px rgba(var(--primary-color-rgb), 0.3); border: none; background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));">
<i class="bi bi-plus-circle me-2"></i>Tambah Kriteria Pertama
<i class="bi bi-plus-circle me-2"></i>Tambah Master Kriteria Pertama
</a>
</div>
@else
@ -629,7 +629,7 @@ class="form-control border-start-0"
<a href="{{ route('kriteria.edit', $item->id) }}"
class="btn btn-sm rounded-3 me-2"
style="background: rgba(var(--warning-color-rgb), 0.15); color: var(--warning-color); border: 1px solid rgba(var(--warning-color-rgb), 0.3); font-weight: 600; transition: all 0.2s ease;"
data-bs-toggle="tooltip" title="Edit Kriteria"
data-bs-toggle="tooltip" title="Edit Master Kriteria"
onmouseover="this.style.background='var(--warning-color)'; this.style.color='white';"
onmouseout="this.style.background='rgba(var(--warning-color-rgb), 0.15)'; this.style.color='var(--warning-color)';">
<i class="bi bi-pencil-square"></i>
@ -749,7 +749,7 @@ class="btn btn-sm rounded-3"
<div class="avatar-sm d-flex align-items-center justify-content-center me-3 rounded-3" style="background: rgba(var(--info-color-rgb), 0.15); color: var(--info-color);">
<i class="bi bi-info-circle-fill" style="font-size: 1.2rem;"></i>
</div>
<h6 class="mb-0 fw-bold" style="color: var(--text-primary);">📋 Informasi Kriteria SAW</h6>
<h6 class="mb-0 fw-bold" style="color: var(--text-primary);">📋 Informasi Master Kriteria SAW</h6>
</div>
<div class="ps-3 border-start border-3" style="border-color: var(--info-color) !important;">
<ul class="mb-0 small" style="color: var(--text-secondary); line-height: 1.6;">

View File

@ -1,6 +1,6 @@
@extends('layouts.admin')
@section('title', 'Detail Kriteria')
@section('title', 'Detail Master Kriteria')
@section('content')
<div class="container-fluid px-4">
@ -75,7 +75,7 @@
<nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ route('dashboard') }}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{{ route('kriteria.index') }}">Kriteria</a></li>
<li class="breadcrumb-item"><a href="{{ route('kriteria.index') }}">Master Kriteria SAW</a></li>
<li class="breadcrumb-item active">Detail</li>
</ol>
</nav>
@ -129,7 +129,7 @@
<i class="bi bi-speedometer text-info fs-4"></i>
</div>
<div class="flex-grow-1">
<small class="text-muted d-block mb-1">Bobot Kriteria</small>
<small class="text-muted d-block mb-1">Bobot Default / Simulasi</small>
<h5 class="mb-0 fw-semibold text-info">
{{ $kriteria->bobot }}
</h5>

View File

@ -732,10 +732,12 @@
<i class="bi bi-building"></i>
<span>Data Kontrakan</span>
</a>
<a href="{{ route('laundry.index') }}" class="menu-item menu-laundry {{ request()->routeIs('laundry.*') ? 'active' : '' }}">
@if(auth('admin')->user()->role === 'super_admin')
<a href="{{ route('laundry.index') }}" class="menu-item menu-laundry {{ request()->routeIs('laundry.*') ? 'active' : '' }}">
<i class="bi bi-water"></i>
<span>Data Laundry</span>
</a>
@endif
<a href="{{ route('admin.bookings.index') }}" class="menu-item {{ request()->routeIs('admin.bookings.*') ? 'active' : '' }}">
<i class="bi bi-calendar-check"></i>
<span>Data Booking</span>
@ -743,34 +745,44 @@
</div>
<!-- Kriteria & SAW -->
@if(auth('admin')->user()->role === 'super_admin')
<div class="menu-section">
<div class="menu-section-title">Sistem SPK</div>
<a href="{{ route('kriteria.index') }}" class="menu-item {{ request()->routeIs('kriteria.*') ? 'active' : '' }}">
<i class="bi bi-list-check"></i>
<span>Kriteria</span>
<span>Master Kriteria SAW</span>
</a>
<a href="{{ route('saw.index') }}" class="menu-item {{ request()->routeIs('saw.index') || request()->routeIs('saw.hasil') ? 'active' : '' }}">
@if(auth('admin')->user()->role === 'super_admin')
<a href="{{ route('saw.index') }}" class="menu-item {{ request()->routeIs('saw.index') || request()->routeIs('saw.hasil') ? 'active' : '' }}">
<i class="bi bi-bar-chart-line"></i>
<span>Hasil Perhitungan</span>
<span>Validasi Perhitungan SAW</span>
</a>
@endif
</div>
@endif
@if(Auth::user()->role === 'super_admin')
<!-- Super Admin Only -->
<div class="menu-section">
<div class="menu-section-title">Super Admin</div>
<a href="{{ route('admin.users.index') }}" class="menu-item {{ request()->routeIs('admin.users.*') ? 'active' : '' }}">
@if(auth('admin')->user()->role === 'super_admin')
<a href="{{ route('admin.users.index') }}" class="menu-item {{ request()->routeIs('admin.users.*') ? 'active' : '' }}">
<i class="bi bi-people"></i>
<span>Manajemen User</span>
</a>
<a href="{{ route('admin.activity-logs.index') }}" class="menu-item {{ request()->routeIs('admin.activity-logs.*') ? 'active' : '' }}">
@endif
@if(auth('admin')->user()->role === 'super_admin')
<a href="{{ route('admin.activity-logs.index') }}" class="menu-item {{ request()->routeIs('admin.activity-logs.*') ? 'active' : '' }}">
<i class="bi bi-clock-history"></i>
<span>Activity Logs</span>
</a>
<a href="{{ route('admin.backup.index') }}" class="menu-item {{ request()->routeIs('admin.backup.*') ? 'active' : '' }}">
@endif
@if(auth('admin')->user()->role === 'super_admin')
<a href="{{ route('admin.backup.index') }}" class="menu-item {{ request()->routeIs('admin.backup.*') ? 'active' : '' }}">
<i class="bi bi-database"></i>
<span>Backup Database</span>
</a>
@endif
</div>
@endif

View File

@ -350,7 +350,7 @@
</div>
</div>
<!-- Enhanced Kriteria & Bobot Card -->
<!-- Enhanced Kriteria & Bobot Simulasi Card -->
<div class="glass-card mb-4">
<div class="card-header border-0 py-3 py-md-4" style="background: rgba(255,255,255,0.95); backdrop-filter: blur(10px);">
<div class="d-flex align-items-center justify-content-between">
@ -359,8 +359,8 @@
<i class="bi bi-star-fill"></i>
</div>
<h6 class="mb-0 fw-bold" style="color: var(--text-primary);">
<span class="d-none d-sm-inline">Kriteria dan Bobot yang Digunakan</span>
<span class="d-sm-none">Kriteria & Bobot</span>
<span class="d-none d-sm-inline">Kriteria dan Bobot Simulasi yang Digunakan</span>
<span class="d-sm-none">Kriteria & Bobot Simulasi</span>
</h6>
</div>
<span class="badge rounded-pill px-3 py-2" style="background: rgba(var(--primary-color-rgb), 0.15); color: var(--primary-color); font-weight: 700;">
@ -663,7 +663,7 @@ class="progress-bar bg-{{ $barColor }}"
<!-- Achievement Badge -->
@if($index == 0)
<div class="alert border-0 mb-3 py-2" style="background: rgba(var(--success-color-rgb), 0.1); border-left: 4px solid var(--success-color) !important;">
<small class="fw-bold" style="color: var(--success-color);"> Rekomendasi Terbaik Berdasarkan Kriteria Anda</small>
<small class="fw-bold" style="color: var(--success-color);"> Rekomendasi Terbaik Berdasarkan Bobot Simulasi</small>
</div>
@elseif($index == 1)
<div class="alert border-0 mb-3 py-2" style="background: rgba(var(--info-color-rgb), 0.1); border-left: 4px solid var(--info-color) !important;">

View File

@ -266,7 +266,7 @@
<table class="table table-sm mb-0 small" id="kriteriaTable">
<thead>
<tr>
<th>Kriteria</th>
<th>Master Kriteria SAW</th>
<th class="text-center">Bobot</th>
<th class="text-center">Tipe</th>
</tr>

View File

@ -391,6 +391,146 @@
.border-admin {
border-color: #667eea !important;
}
.modern-section-card {
border: none;
border-radius: 18px;
box-shadow: 0 14px 34px rgba(15, 23, 42, 0.08);
overflow: hidden;
background: #ffffff;
height: 100%;
}
.modern-section-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
padding: 18px 22px;
border-bottom: 1px solid rgba(226, 232, 240, 0.9);
background: linear-gradient(135deg, rgba(102, 126, 234, 0.08), rgba(255, 255, 255, 0.95));
}
.modern-section-title {
display: flex;
align-items: center;
gap: 10px;
font-weight: 800;
font-size: 1.05rem;
color: #4f46e5;
margin: 0;
}
.modern-count-badge {
background: rgba(99, 102, 241, 0.12);
color: #4f46e5;
font-size: 0.75rem;
font-weight: 700;
padding: 6px 12px;
border-radius: 999px;
white-space: nowrap;
}
.modern-list {
padding: 16px 18px 8px 18px;
}
.modern-item {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 14px;
padding: 14px 12px;
border-radius: 14px;
transition: all 0.25s ease;
border: 1px solid rgba(226, 232, 240, 0.8);
margin-bottom: 10px;
background: #fafbff;
}
.modern-item:hover {
background: #f4f6ff;
border-color: rgba(99, 102, 241, 0.28);
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.06);
}
.modern-item-left {
flex: 1;
min-width: 0;
}
.modern-item-title {
font-weight: 800;
font-size: 0.96rem;
color: #111827;
margin-bottom: 6px;
}
.modern-item-subtitle {
font-size: 0.82rem;
color: #64748b;
line-height: 1.5;
margin-bottom: 8px;
}
.modern-meta {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.modern-meta-badge {
font-size: 0.72rem;
padding: 5px 10px;
border-radius: 999px;
font-weight: 700;
background: #eef2ff;
color: #4f46e5;
}
.modern-price {
min-width: 115px;
text-align: center;
padding: 9px 12px;
border-radius: 12px;
font-size: 0.82rem;
font-weight: 800;
white-space: nowrap;
background: #dcfce7;
color: #15803d;
}
.modern-price.kontrakan {
background: #eef2ff;
color: #4f46e5;
}
.modern-empty {
text-align: center;
padding: 36px 20px;
color: #94a3b8;
}
.modern-footer {
padding: 4px 18px 18px 18px;
}
.modern-btn {
border-radius: 12px;
font-weight: 700;
padding: 9px 15px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
color: #fff;
}
.modern-btn:hover {
color: #fff;
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.25);
}
</style>
<!-- Header Section -->