new
This commit is contained in:
parent
0c4ac2449d
commit
9f1df18c43
|
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Classification;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class AdminController extends Controller
|
||||||
|
{
|
||||||
|
public function dashboard()
|
||||||
|
{
|
||||||
|
$totalUsers = User::where('role', 'user')->count();
|
||||||
|
$totalClassifications = Classification::count();
|
||||||
|
|
||||||
|
$statistikKelas = Classification::select('label', DB::raw('count(*) as total'))
|
||||||
|
->groupBy('label')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return view('admin.dashboard', compact('totalUsers', 'totalClassifications', 'statistikKelas'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function users()
|
||||||
|
{
|
||||||
|
$users = User::where('role', 'user')->latest()->get();
|
||||||
|
return view('admin.users', compact('users'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroyUser($id)
|
||||||
|
{
|
||||||
|
$user = User::findOrFail($id);
|
||||||
|
|
||||||
|
if ($user->role === 'admin') {
|
||||||
|
return back()->withErrors(['error' => 'Akun admin tidak dapat dihapus.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->delete();
|
||||||
|
|
||||||
|
return back()->with('success', 'Pengguna berhasil dihapus.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exportCsv()
|
||||||
|
{
|
||||||
|
$classifications = Classification::all();
|
||||||
|
|
||||||
|
$filename = "laporan_klasifikasi_kopi.csv";
|
||||||
|
$handle = fopen('php://memory', 'w');
|
||||||
|
|
||||||
|
// Membuat Baris Header CSV
|
||||||
|
fputcsv($handle, ['ID', 'User ID', 'Hasil Klasifikasi', 'Akurasi (%)', 'Tanggal Pemindaian']);
|
||||||
|
|
||||||
|
// Mengisi Baris Data
|
||||||
|
foreach ($classifications as $row) {
|
||||||
|
fputcsv($handle, [
|
||||||
|
$row->id,
|
||||||
|
$row->user_id,
|
||||||
|
$row->label,
|
||||||
|
$row->confidence,
|
||||||
|
$row->created_at
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek($handle, 0);
|
||||||
|
$headers = [
|
||||||
|
'Content-Type' => 'text/csv',
|
||||||
|
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
|
||||||
|
];
|
||||||
|
|
||||||
|
return response()->stream(function () use ($handle) {
|
||||||
|
fpassthru($handle);
|
||||||
|
}, 200, $headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -64,5 +64,6 @@ class Kernel extends HttpKernel
|
||||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||||
|
'admin' => \App\Http\Middleware\AdminMiddleware::class,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class AdminMiddleware
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
if (!auth()->check() || auth()->user()->role !== 'admin') {
|
||||||
|
abort(403, 'Akses ditolak. Halaman ini khusus untuk Admin.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,8 +21,11 @@ class User extends Authenticatable
|
||||||
'name',
|
'name',
|
||||||
'email',
|
'email',
|
||||||
'password',
|
'password',
|
||||||
|
'role',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The attributes that should be hidden for serialization.
|
* The attributes that should be hidden for serialization.
|
||||||
*
|
*
|
||||||
|
|
@ -42,4 +45,8 @@ class User extends Authenticatable
|
||||||
'email_verified_at' => 'datetime',
|
'email_verified_at' => 'datetime',
|
||||||
'password' => 'hashed',
|
'password' => 'hashed',
|
||||||
];
|
];
|
||||||
|
public function isAdmin(): bool
|
||||||
|
{
|
||||||
|
return $this->role === 'admin';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?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('users', function (Blueprint $table) {
|
||||||
|
$table->enum('role', ['user', 'admin'])->default('user')->after('password');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('role');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="max-w-6xl mx-auto px-10 py-16">
|
||||||
|
<h1 class="text-3xl font-extrabold text-gray-900 mb-8">Dashboard Admin</h1>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-orange-100 p-8">
|
||||||
|
<h3 class="text-sm font-bold text-gray-500 uppercase tracking-widest mb-2">Total Pengguna Terdaftar</h3>
|
||||||
|
<p class="text-4xl font-black text-orange-700">{{ $totalUsers }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-orange-100 p-8">
|
||||||
|
<h3 class="text-sm font-bold text-gray-500 uppercase tracking-widest mb-2">Total Klasifikasi Dilakukan</h3>
|
||||||
|
<p class="text-4xl font-black text-orange-700">{{ $totalClassifications }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-orange-100 p-8 mb-8">
|
||||||
|
<h3 class="text-sm font-bold text-gray-500 uppercase tracking-widest mb-6">Statistik Klasifikasi per Kelas</h3>
|
||||||
|
<div class="space-y-4">
|
||||||
|
@forelse ($statistikKelas as $item)
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="font-bold text-gray-900 capitalize">{{ $item->label }}</span>
|
||||||
|
<span class="px-4 py-1 bg-orange-50 text-orange-700 rounded-full text-sm font-bold">{{ $item->total }} kali</span>
|
||||||
|
</div>
|
||||||
|
@empty
|
||||||
|
<p class="text-gray-400">Belum ada data klasifikasi.</p>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="{{ route('admin.users') }}" class="inline-block px-8 py-4 bg-orange-700 text-white rounded-full font-bold shadow-lg hover:bg-orange-800 transition">
|
||||||
|
Kelola Data Pengguna
|
||||||
|
</a>
|
||||||
|
<a href="{{ route('admin.export') }}" class="inline-block text-center px-8 py-4 bg-gray-800 text-white rounded-full font-bold shadow-lg hover:bg-black transition">
|
||||||
|
Export Data CSV
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="max-w-6xl mx-auto px-10 py-16">
|
||||||
|
<h1 class="text-3xl font-extrabold text-gray-900 mb-8">Kelola Data Pengguna</h1>
|
||||||
|
|
||||||
|
@if (session('success'))
|
||||||
|
<div class="mb-6 px-6 py-4 bg-green-50 text-green-700 rounded-2xl border border-green-200">
|
||||||
|
{{ session('success') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($errors->any())
|
||||||
|
<div class="mb-6 px-6 py-4 bg-red-50 text-red-700 rounded-2xl border border-red-200">
|
||||||
|
{{ $errors->first() }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-orange-100 overflow-hidden">
|
||||||
|
<table class="w-full text-left">
|
||||||
|
<thead class="bg-orange-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-4 text-xs font-bold text-gray-500 uppercase tracking-widest">Nama</th>
|
||||||
|
<th class="px-6 py-4 text-xs font-bold text-gray-500 uppercase tracking-widest">Email</th>
|
||||||
|
<th class="px-6 py-4 text-xs font-bold text-gray-500 uppercase tracking-widest">Terdaftar</th>
|
||||||
|
<th class="px-6 py-4 text-xs font-bold text-gray-500 uppercase tracking-widest">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse ($users as $user)
|
||||||
|
<tr class="border-t border-gray-100">
|
||||||
|
<td class="px-6 py-4">{{ $user->name }}</td>
|
||||||
|
<td class="px-6 py-4">{{ $user->email }}</td>
|
||||||
|
<td class="px-6 py-4">{{ $user->created_at->format('d-m-Y') }}</td>
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
<form action="{{ route('admin.users.destroy', $user->id) }}" method="POST"
|
||||||
|
onsubmit="return confirm('Yakin ingin menghapus pengguna ini?');">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit" class="px-4 py-2 bg-red-50 text-red-700 rounded-full text-xs font-bold hover:bg-red-100 transition">
|
||||||
|
Hapus
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="px-6 py-8 text-center text-gray-400">Belum ada pengguna terdaftar.</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="{{ route('admin.dashboard') }}" class="inline-block mt-8 text-orange-700 font-bold hover:text-orange-800">
|
||||||
|
← Kembali ke Dashboard
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
@ -33,6 +33,12 @@
|
||||||
@endguest
|
@endguest
|
||||||
@auth
|
@auth
|
||||||
<div class="flex items-center gap-6">
|
<div class="flex items-center gap-6">
|
||||||
|
@if(Auth::user()->role === 'admin')
|
||||||
|
<a href="{{ route('admin.dashboard') }}" class="px-5 py-2 bg-orange-700 text-white text-xs font-bold rounded-full hover:bg-orange-800 transition">
|
||||||
|
Admin Panel
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
<div class="flex flex-col items-end">
|
<div class="flex flex-col items-end">
|
||||||
<span class="text-[10px] font-black uppercase text-gray-400 tracking-widest">Logged in as</span>
|
<span class="text-[10px] font-black uppercase text-gray-400 tracking-widest">Logged in as</span>
|
||||||
<span class="text-sm font-bold text-gray-900">{{ Auth::user()->name }}</span>
|
<span class="text-sm font-bold text-gray-900">{{ Auth::user()->name }}</span>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use App\Http\Controllers\AuthController;
|
use App\Http\Controllers\AuthController;
|
||||||
use App\Http\Controllers\ClassificationController;
|
use App\Http\Controllers\ClassificationController;
|
||||||
|
use App\Http\Controllers\AdminController;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
@ -73,5 +74,12 @@
|
||||||
Route::post('/logout', [AuthController::class, 'logout'])->name('logout');
|
Route::post('/logout', [AuthController::class, 'logout'])->name('logout');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(function () {
|
||||||
|
Route::get('/dashboard', [AdminController::class, 'dashboard'])->name('dashboard');
|
||||||
|
Route::get('/users', [AdminController::class, 'users'])->name('users');
|
||||||
|
Route::delete('/users/{id}', [AdminController::class, 'destroyUser'])->name('users.destroy');
|
||||||
|
Route::get('/export', [AdminController::class, 'exportCsv'])->name('export');
|
||||||
|
});
|
||||||
|
|
||||||
Route::post('/api/classify', [ClassificationController::class, 'process'])->name('api.classify');
|
Route::post('/api/classify', [ClassificationController::class, 'process'])->name('api.classify');
|
||||||
Route::post('/api/save-result', [ClassificationController::class, 'store'])->name('api.save');
|
Route::post('/api/save-result', [ClassificationController::class, 'store'])->name('api.save');
|
||||||
Loading…
Reference in New Issue