menambahakan history dan beberapa dataset
This commit is contained in:
parent
582d1c1833
commit
0372164658
|
|
@ -0,0 +1,107 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Dataset;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class ImportDatasetImages extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'import:dataset {--force : Force import even if records exist}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Import rice leaf disease images into the dataset table';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$force = $this->option('force');
|
||||||
|
|
||||||
|
// Define disease types and their source directories
|
||||||
|
$diseases = [
|
||||||
|
'Bacterial Blight' => 'd:\\PROJECT TA\\rice leaf diseases dataset\\Bacterialblight',
|
||||||
|
'Brown Spot' => 'd:\\PROJECT TA\\rice leaf diseases dataset\\Brownspot',
|
||||||
|
'Leaf Smut' => 'd:\\PROJECT TA\\rice leaf diseases dataset\\Leafsmut',
|
||||||
|
];
|
||||||
|
|
||||||
|
$totalImported = 0;
|
||||||
|
|
||||||
|
foreach ($diseases as $label => $sourcePath) {
|
||||||
|
if (!File::exists($sourcePath)) {
|
||||||
|
$this->error("Source path not found: $sourcePath");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all image files
|
||||||
|
$imageFiles = File::files($sourcePath);
|
||||||
|
$imageCount = count($imageFiles);
|
||||||
|
|
||||||
|
$this->info("\n--- Processing: $label ---");
|
||||||
|
$this->info("Found $imageCount images in: $sourcePath");
|
||||||
|
|
||||||
|
$bar = $this->output->createProgressBar($imageCount);
|
||||||
|
$bar->start();
|
||||||
|
|
||||||
|
$importedCount = 0;
|
||||||
|
|
||||||
|
foreach ($imageFiles as $file) {
|
||||||
|
// Check if file is an image
|
||||||
|
$extension = strtolower($file->getExtension());
|
||||||
|
if (!in_array($extension, ['jpg', 'jpeg', 'png', 'gif'])) {
|
||||||
|
$bar->advance();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate unique filename to avoid conflicts
|
||||||
|
$filename = time() . '_' . md5($file->getPathname()) . '.' . $extension;
|
||||||
|
$storagePath = 'datasets/' . $filename;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if file already exists in database
|
||||||
|
$exists = Dataset::where('image_path', $storagePath)->exists();
|
||||||
|
|
||||||
|
if (!$exists || $force) {
|
||||||
|
// Copy file to storage
|
||||||
|
$fileContents = File::get($file->getPathname());
|
||||||
|
Storage::disk('public')->put($storagePath, $fileContents);
|
||||||
|
|
||||||
|
// Create or update database record
|
||||||
|
Dataset::updateOrCreate(
|
||||||
|
['image_path' => $storagePath],
|
||||||
|
['label' => $label]
|
||||||
|
);
|
||||||
|
|
||||||
|
$importedCount++;
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->warn("Error processing {$file->getFilename()}: {$e->getMessage()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
$bar->advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
$bar->finish();
|
||||||
|
$this->info("\n✓ Imported $importedCount images with label: $label");
|
||||||
|
$totalImported += $importedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info("\n" . str_repeat('=', 50));
|
||||||
|
$this->info("✓ Import Complete!");
|
||||||
|
$this->info("Total images imported: $totalImported");
|
||||||
|
$this->info("Total dataset records: " . Dataset::count());
|
||||||
|
$this->info(str_repeat('=', 50));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\ClassificationHistory;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ClassificationHistoryAdminController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$query = ClassificationHistory::with('user')->latest();
|
||||||
|
|
||||||
|
if ($request->filled('jenis_penyakit')) {
|
||||||
|
$query->where('jenis_penyakit', 'like', '%' . $request->jenis_penyakit . '%');
|
||||||
|
}
|
||||||
|
|
||||||
|
$histories = $query->paginate(10)->withQueryString();
|
||||||
|
$users = User::orderBy('name')->get();
|
||||||
|
|
||||||
|
return view('admin.classification_histories.index', compact('histories', 'users'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$validated = $request->validate([
|
||||||
|
'user_id' => 'required|exists:users,id',
|
||||||
|
'jenis_penyakit' => 'required|string|max:255',
|
||||||
|
]);
|
||||||
|
|
||||||
|
ClassificationHistory::create($validated);
|
||||||
|
|
||||||
|
return back()->with('success', 'Data history klasifikasi berhasil ditambahkan.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, string $id)
|
||||||
|
{
|
||||||
|
$validated = $request->validate([
|
||||||
|
'user_id' => 'required|exists:users,id',
|
||||||
|
'jenis_penyakit' => 'required|string|max:255',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$history = ClassificationHistory::findOrFail($id);
|
||||||
|
$history->update($validated);
|
||||||
|
|
||||||
|
return back()->with('success', 'Data history klasifikasi berhasil diperbarui.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(string $id)
|
||||||
|
{
|
||||||
|
$history = ClassificationHistory::findOrFail($id);
|
||||||
|
$history->delete();
|
||||||
|
|
||||||
|
return back()->with('success', 'Data history klasifikasi berhasil dihapus.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Dataset;
|
||||||
|
use App\Models\Product;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class DashboardController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
// Hitung total dataset
|
||||||
|
$totalDataset = Dataset::count();
|
||||||
|
|
||||||
|
// Statistik per penyakit
|
||||||
|
$diseaseStats = Dataset::selectRaw('label, count(*) as total')
|
||||||
|
->groupBy('label')
|
||||||
|
->orderByDesc('total')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
// Penyakit dengan jumlah terbanyak
|
||||||
|
$mostCommonDisease = $diseaseStats->first();
|
||||||
|
|
||||||
|
// Total produk
|
||||||
|
$totalProducts = Product::count();
|
||||||
|
|
||||||
|
return view('dashboard', compact(
|
||||||
|
'totalDataset',
|
||||||
|
'diseaseStats',
|
||||||
|
'mostCommonDisease',
|
||||||
|
'totalProducts'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,10 +6,24 @@
|
||||||
|
|
||||||
class DatasetController extends Controller
|
class DatasetController extends Controller
|
||||||
{
|
{
|
||||||
public function index() {
|
public function index(Request $request) {
|
||||||
$datasets = Dataset::all();
|
// Get filter parameters
|
||||||
// Hitung jumlah per label untuk statistik mini
|
$label = $request->get('label');
|
||||||
|
|
||||||
|
// Build query
|
||||||
|
$query = Dataset::query();
|
||||||
|
|
||||||
|
// Apply label filter
|
||||||
|
if ($label) {
|
||||||
|
$query->where('label', $label);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get paginated datasets (15 per page) and keep current filters in pagination links
|
||||||
|
$datasets = $query->orderByDesc('id')->paginate(15)->withQueryString();
|
||||||
|
|
||||||
|
// Get statistics
|
||||||
$stats = Dataset::selectRaw('label, count(*) as total')->groupBy('label')->get();
|
$stats = Dataset::selectRaw('label, count(*) as total')->groupBy('label')->get();
|
||||||
|
|
||||||
return view('admin.datasets.index', compact('datasets', 'stats'));
|
return view('admin.datasets.index', compact('datasets', 'stats'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class ClassificationHistory extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'user_id',
|
||||||
|
'jenis_penyakit',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('classification_histories', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->string('jenis_penyakit');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('classification_histories');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
@extends('layouts.admin')
|
||||||
|
@section('title', 'History Classification')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
|
||||||
|
{{ session('success') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($errors->any())
|
||||||
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
||||||
|
<ul class="list-disc pl-5 text-sm">
|
||||||
|
@foreach($errors->all() as $error)
|
||||||
|
<li>{{ $error }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
|
<div class="p-4 border-b bg-gray-50 flex flex-col sm:flex-row gap-3 sm:items-center sm:justify-between">
|
||||||
|
<h3 class="text-lg font-bold text-gray-800">Data History Classification</h3>
|
||||||
|
<form method="GET" class="flex gap-2">
|
||||||
|
<input type="text" name="jenis_penyakit" value="{{ request('jenis_penyakit') }}" placeholder="Filter penyakit..." class="border border-gray-300 rounded-lg p-2 text-sm">
|
||||||
|
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-2 rounded-lg text-sm">Filter</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">ID</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">User</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Jenis Penyakit</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase">Waktu</th>
|
||||||
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">Aksi</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white divide-y divide-gray-200">
|
||||||
|
@forelse($histories as $history)
|
||||||
|
<tr>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-700">{{ $history->id }}</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-700">{{ $history->user->name ?? '-' }}</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-700">{{ $history->jenis_penyakit }}</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-500">{{ $history->created_at?->format('d M Y H:i') }}</td>
|
||||||
|
<td class="px-4 py-3 text-right text-sm">
|
||||||
|
<button type="button" onclick="toggleEdit({{ $history->id }})" class="text-blue-600 hover:text-blue-800 mr-3">Edit</button>
|
||||||
|
<form action="{{ route('history-klasifikasi.destroy', $history->id) }}" method="POST" class="inline" onsubmit="return confirm('Yakin hapus data ini?')">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit" class="text-red-600 hover:text-red-800">Hapus</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="edit-row-{{ $history->id }}" class="hidden bg-gray-50">
|
||||||
|
<td colspan="5" class="px-4 py-3">
|
||||||
|
<form action="{{ route('history-klasifikasi.update', $history->id) }}" method="POST" class="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||||
|
@csrf
|
||||||
|
@method('PUT')
|
||||||
|
|
||||||
|
<select name="user_id" class="border border-gray-300 rounded-lg p-2 text-sm" required>
|
||||||
|
@foreach($users as $user)
|
||||||
|
<option value="{{ $user->id }}" {{ $history->user_id == $user->id ? 'selected' : '' }}>{{ $user->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input type="text" name="jenis_penyakit" value="{{ $history->jenis_penyakit }}" class="border border-gray-300 rounded-lg p-2 text-sm" required>
|
||||||
|
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button type="submit" class="bg-emerald-600 text-white px-3 py-2 rounded-lg text-sm">Update</button>
|
||||||
|
<button type="button" onclick="toggleEdit({{ $history->id }})" class="bg-gray-200 text-gray-700 px-3 py-2 rounded-lg text-sm">Batal</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="px-4 py-8 text-center text-sm text-gray-500">Belum ada data history classification.</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-4 border-t bg-white">
|
||||||
|
{{ $histories->onEachSide(1)->links() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleEdit(id) {
|
||||||
|
const row = document.getElementById('edit-row-' + id);
|
||||||
|
if (!row) return;
|
||||||
|
row.classList.toggle('hidden');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
@endsection
|
||||||
|
|
@ -9,7 +9,12 @@
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
<!-- Statistics Cards -->
|
||||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
|
||||||
|
<div class="bg-white p-4 rounded-xl shadow-sm border border-gray-100">
|
||||||
|
<p class="text-xs text-gray-400 uppercase">Total Data</p>
|
||||||
|
<p class="text-2xl font-bold text-blue-600">{{ $stats->sum('total') }} <span class="text-xs text-gray-400 font-normal">Citra</span></p>
|
||||||
|
</div>
|
||||||
@foreach($stats as $stat)
|
@foreach($stats as $stat)
|
||||||
<div class="bg-white p-4 rounded-xl shadow-sm border border-gray-100">
|
<div class="bg-white p-4 rounded-xl shadow-sm border border-gray-100">
|
||||||
<p class="text-xs text-gray-400 uppercase">{{ $stat->label }}</p>
|
<p class="text-xs text-gray-400 uppercase">{{ $stat->label }}</p>
|
||||||
|
|
@ -18,18 +23,19 @@
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Upload Form and Gallery Section -->
|
||||||
<div class="flex flex-col lg:flex-row gap-8">
|
<div class="flex flex-col lg:flex-row gap-8">
|
||||||
<div class="w-full lg:w-1/3">
|
<div class="w-full lg:w-1/3">
|
||||||
<div class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 sticky top-4">
|
<div class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 sticky top-4">
|
||||||
<h3 class="text-lg font-bold text-gray-800 mb-4 border-b pb-2">Upload Data Latih</h3>
|
<h3 class="text-lg font-bold text-gray-800 mb-4 border-b pb-2">📤 Upload Data Latih</h3>
|
||||||
<form action="{{ route('dataset.store') }}" method="POST" enctype="multipart/form-data">
|
<form action="{{ route('dataset.store') }}" method="POST" enctype="multipart/form-data">
|
||||||
@csrf
|
@csrf
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Label Penyakit</label>
|
<label class="block text-sm font-medium text-gray-700 mb-1">Label Penyakit</label>
|
||||||
<select name="label" class="w-full border-gray-300 rounded-lg shadow-sm p-2 border">
|
<select name="label" class="w-full border-gray-300 rounded-lg shadow-sm p-2 border">
|
||||||
|
<option value="Bacterial Blight">Bacterial Blight</option>
|
||||||
<option value="Brown Spot">Brown Spot</option>
|
<option value="Brown Spot">Brown Spot</option>
|
||||||
<option value="Leaf Smut">Leaf Smut</option>
|
<option value="Leaf Smut">Leaf Smut</option>
|
||||||
<option value="Bacterial Leaf Blight">Bacterial Leaf Blight</option>
|
|
||||||
<option value="Healthy">Healthy (Sehat)</option>
|
<option value="Healthy">Healthy (Sehat)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -40,25 +46,51 @@
|
||||||
<p class="text-xs text-gray-400 mt-1">Format: JPG, PNG. Max: 2MB</p>
|
<p class="text-xs text-gray-400 mt-1">Format: JPG, PNG. Max: 2MB</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="w-full bg-emerald-600 hover:bg-emerald-700 text-white font-bold py-2 rounded-lg transition">Upload Dataset</button>
|
<button type="submit" class="w-full bg-emerald-600 hover:bg-emerald-700 text-white font-bold py-2 rounded-lg transition">
|
||||||
|
✓ Upload Dataset
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr class="my-6">
|
||||||
|
|
||||||
|
<h4 class="text-sm font-bold text-gray-800 mb-3">🔍 Filter Galeri</h4>
|
||||||
|
<form method="GET" class="space-y-2">
|
||||||
|
<select name="label" class="w-full border border-gray-300 rounded-lg p-2 text-sm">
|
||||||
|
<option value="">Semua Label</option>
|
||||||
|
@foreach($stats as $stat)
|
||||||
|
<option value="{{ $stat->label }}" {{ request('label') == $stat->label ? 'selected' : '' }}>
|
||||||
|
{{ $stat->label }} ({{ $stat->total }})
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button type="submit" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 rounded-lg transition text-sm">
|
||||||
|
Filter
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full lg:w-2/3">
|
<div class="w-full lg:w-2/3">
|
||||||
<div class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
|
<div class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
|
||||||
<h3 class="text-lg font-bold text-gray-800 mb-4">Galeri Data</h3>
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-2 mb-4 border-b pb-2">
|
||||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
<h3 class="text-lg font-bold text-gray-800">📸 Galeri Data ({{ $datasets->total() }} Item)</h3>
|
||||||
|
<span class="text-xs bg-blue-100 text-blue-700 px-3 py-1 rounded-full">Page {{ $datasets->currentPage() }} of {{ $datasets->lastPage() }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if($datasets->count() > 0)
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-3 gap-4 mb-6">
|
||||||
@foreach($datasets as $data)
|
@foreach($datasets as $data)
|
||||||
<div class="relative group rounded-lg overflow-hidden border border-gray-200">
|
<div class="relative group rounded-lg overflow-hidden border border-gray-200 hover:border-emerald-400 transition">
|
||||||
<img src="{{ asset('storage/' . $data->image_path) }}" class="w-full h-32 object-cover" alt="{{ $data->label }}">
|
<img src="{{ asset('storage/' . $data->image_path) }}" class="w-full h-32 object-cover hover:scale-105 transition" alt="{{ $data->label }}">
|
||||||
<div class="absolute bottom-0 left-0 right-0 bg-black/60 p-2">
|
<div class="absolute bottom-0 left-0 right-0 bg-black/60 p-2">
|
||||||
<p class="text-white text-xs font-bold">{{ $data->label }}</p>
|
<p class="text-white text-xs font-bold">{{ $data->label }}</p>
|
||||||
|
<p class="text-gray-300 text-xs">ID: {{ $data->id }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition">
|
<div class="absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition">
|
||||||
<form action="{{ route('dataset.destroy', $data->id) }}" method="POST">
|
<form action="{{ route('dataset.destroy', $data->id) }}" method="POST" onsubmit="return confirm('Hapus gambar ini?')">
|
||||||
@csrf @method('DELETE')
|
@csrf @method('DELETE')
|
||||||
<button type="submit" class="bg-red-600 text-white p-1 rounded-full hover:bg-red-700" title="Hapus Gambar">
|
<button type="submit" class="bg-red-600 text-white p-1.5 rounded-full hover:bg-red-700 shadow-lg" title="Hapus Gambar">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
@ -68,6 +100,18 @@
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination (compact and responsive) -->
|
||||||
|
<div class="mt-6 pt-4 border-t overflow-x-auto">
|
||||||
|
<div class="min-w-max flex justify-center">
|
||||||
|
{{ $datasets->onEachSide(1)->links() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="text-center py-12">
|
||||||
|
<p class="text-gray-500 text-lg">📭 Tidak ada data yang ditemukan</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,26 +4,29 @@
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
|
<!-- Kartu Statistik Utama -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
|
||||||
|
|
||||||
|
<!-- Kartu 1: Total Dataset (Citra Penyakit) -->
|
||||||
<div class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 flex items-start justify-between">
|
<div class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 flex items-start justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">Total Diagnosa</p>
|
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">📊 Total Data Latih</p>
|
||||||
<h3 class="text-3xl font-bold text-gray-800 mt-2">1,240</h3>
|
<h3 class="text-3xl font-bold text-gray-800 mt-2">{{ number_format($totalDataset) }}</h3>
|
||||||
<span class="inline-block mt-2 px-2 py-1 text-xs font-bold text-emerald-600 bg-emerald-50 rounded-md">+12% Bulan ini</span>
|
<span class="inline-block mt-2 px-2 py-1 text-xs font-bold text-blue-600 bg-blue-50 rounded-md">{{ $totalDataset }} Citra Penyakit</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-3 bg-blue-50 text-blue-600 rounded-xl">
|
<div class="p-3 bg-blue-50 text-blue-600 rounded-xl">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Kartu 2: Total Produk -->
|
||||||
<div class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 flex items-start justify-between">
|
<div class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 flex items-start justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">Total Produk</p>
|
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">🛒 Total Produk</p>
|
||||||
<h3 class="text-3xl font-bold text-gray-800 mt-2">24</h3>
|
<h3 class="text-3xl font-bold text-gray-800 mt-2">{{ $totalProducts }}</h3>
|
||||||
<span class="inline-block mt-2 text-xs text-gray-500">Siap Jual</span>
|
<span class="inline-block mt-2 text-xs text-gray-500">Solusi Pertanian Siap Jual</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-3 bg-purple-50 text-purple-600 rounded-xl">
|
<div class="p-3 bg-purple-50 text-purple-600 rounded-xl">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
|
@ -32,11 +35,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Kartu 3: Penyakit Terbanyak di Database -->
|
||||||
<div class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 flex items-start justify-between">
|
<div class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100 flex items-start justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">Penyakit Terbanyak</p>
|
<p class="text-xs font-semibold text-gray-400 uppercase tracking-wider">⚠️ Penyakit Terbanyak</p>
|
||||||
<h3 class="text-xl font-bold text-gray-800 mt-2">Brown Spot</h3>
|
<h3 class="text-xl font-bold text-gray-800 mt-2">{{ $mostCommonDisease->label ?? 'N/A' }}</h3>
|
||||||
<span class="inline-block mt-2 text-xs text-gray-500">450 Kasus terdeteksi</span>
|
<span class="inline-block mt-2 text-xs text-gray-500">
|
||||||
|
{{ $mostCommonDisease->total ?? 0 }} Data dalam database
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-3 bg-red-50 text-red-600 rounded-xl">
|
<div class="p-3 bg-red-50 text-red-600 rounded-xl">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
|
@ -46,40 +52,39 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Grafik dan Detail Penyakit -->
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
|
|
||||||
|
<!-- Grafik Distribusi Penyakit -->
|
||||||
<div class="lg:col-span-2 bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
|
<div class="lg:col-span-2 bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
|
||||||
<h3 class="text-lg font-bold text-gray-800 mb-4">Statistik Penyakit (Bulan Ini)</h3>
|
<h3 class="text-lg font-bold text-gray-800 mb-4">📈 Distribusi Data Penyakit dalam Database</h3>
|
||||||
<canvas id="diseaseChart" height="150"></canvas>
|
<div class="h-56 md:h-64">
|
||||||
|
<canvas id="diseaseChart"></canvas>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-500 mt-4">Diagram menunjukkan jumlah citra setiap jenis penyakit yang tersimpan di database untuk training AI</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Daftar Detail Penyakit -->
|
||||||
<div class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
|
<div class="bg-white p-6 rounded-2xl shadow-sm border border-gray-100">
|
||||||
<h3 class="text-lg font-bold text-gray-800 mb-4">Produk Paling Sering Direkomendasikan</h3>
|
<h3 class="text-lg font-bold text-gray-800 mb-4">📋 Detail Data Penyakit</h3>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex items-center gap-4">
|
@forelse($diseaseStats as $disease)
|
||||||
<div class="w-10 h-10 rounded-lg bg-gray-100 flex items-center justify-center text-lg">💊</div>
|
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg border border-gray-200">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h4 class="text-sm font-bold text-gray-800">Fungisida Score 250</h4>
|
<h4 class="text-sm font-bold text-gray-800">{{ $disease->label }}</h4>
|
||||||
<p class="text-xs text-gray-500">Untuk Brown Spot</p>
|
<p class="text-xs text-gray-500 mt-1">
|
||||||
|
<span class="font-semibold text-blue-600">{{ $disease->total }}</span> Citra
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="font-bold text-emerald-600">84x</span>
|
<div class="text-right">
|
||||||
|
<div class="w-12 h-12 rounded-lg bg-gradient-to-br from-emerald-400 to-emerald-600 flex items-center justify-center text-white font-bold">
|
||||||
|
{{ round(($disease->total / $totalDataset) * 100) }}%
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<div class="w-10 h-10 rounded-lg bg-gray-100 flex items-center justify-center text-lg">🌿</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
<h4 class="text-sm font-bold text-gray-800">Pupuk Urea</h4>
|
|
||||||
<p class="text-xs text-gray-500">Penyubur Daun</p>
|
|
||||||
</div>
|
</div>
|
||||||
<span class="font-bold text-emerald-600">56x</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-4">
|
|
||||||
<div class="w-10 h-10 rounded-lg bg-gray-100 flex items-center justify-center text-lg">🧪</div>
|
|
||||||
<div class="flex-1">
|
|
||||||
<h4 class="text-sm font-bold text-gray-800">Bakterisida Agrept</h4>
|
|
||||||
<p class="text-xs text-gray-500">Untuk Leaf Blight</p>
|
|
||||||
</div>
|
|
||||||
<span class="font-bold text-emerald-600">32x</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
@empty
|
||||||
|
<p class="text-gray-500 text-sm">Belum ada data penyakit</p>
|
||||||
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -89,27 +94,69 @@
|
||||||
const myChart = new Chart(ctx, {
|
const myChart = new Chart(ctx, {
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
data: {
|
data: {
|
||||||
labels: ['Brown Spot', 'Leaf Blast', 'Hispa', 'Healthy'],
|
labels: [
|
||||||
datasets: [{
|
@foreach($diseaseStats as $disease)
|
||||||
label: 'Jumlah Kasus',
|
'{{ $disease->label }}',
|
||||||
data: [45, 30, 20, 55], // Data Dummy
|
@endforeach
|
||||||
backgroundColor: [
|
|
||||||
'rgba(239, 68, 68, 0.7)', // Merah
|
|
||||||
'rgba(245, 158, 11, 0.7)', // Kuning
|
|
||||||
'rgba(59, 130, 246, 0.7)', // Biru
|
|
||||||
'rgba(16, 185, 129, 0.7)' // Hijau
|
|
||||||
],
|
],
|
||||||
|
datasets: [{
|
||||||
|
label: 'Jumlah Citra',
|
||||||
|
data: [
|
||||||
|
@foreach($diseaseStats as $disease)
|
||||||
|
{{ $disease->total }},
|
||||||
|
@endforeach
|
||||||
|
],
|
||||||
|
backgroundColor: [
|
||||||
|
'rgba(239, 68, 68, 0.8)', // Merah (Brown Spot)
|
||||||
|
'rgba(245, 158, 11, 0.8)', // Kuning (Leaf Smut)
|
||||||
|
'rgba(59, 130, 246, 0.8)', // Biru (Bacterial Blight)
|
||||||
|
'rgba(16, 185, 129, 0.8)' // Hijau (Healthy)
|
||||||
|
],
|
||||||
|
borderColor: [
|
||||||
|
'rgba(239, 68, 68, 1)',
|
||||||
|
'rgba(245, 158, 11, 1)',
|
||||||
|
'rgba(59, 130, 246, 1)',
|
||||||
|
'rgba(16, 185, 129, 1)'
|
||||||
|
],
|
||||||
|
borderWidth: 2,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
|
maxBarThickness: 72,
|
||||||
|
barPercentage: 0.9,
|
||||||
|
categoryPercentage: 0.8,
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
responsive: true,
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: { display: false }
|
legend: {
|
||||||
|
display: false,
|
||||||
|
position: 'bottom',
|
||||||
|
labels: {
|
||||||
|
font: { size: 12 },
|
||||||
|
padding: 15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function(context) {
|
||||||
|
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
||||||
|
const value = context.parsed.y;
|
||||||
|
const percentage = ((value / total) * 100).toFixed(1);
|
||||||
|
return context.label + ': ' + value + ' (' + percentage + '%)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
y: { beginAtZero: true, grid: { display: false } },
|
y: {
|
||||||
x: { grid: { display: false } }
|
beginAtZero: true,
|
||||||
|
grid: { color: 'rgba(229, 231, 235, 0.7)' },
|
||||||
|
ticks: { precision: 0 }
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
grid: { display: false }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,13 @@
|
||||||
</svg>
|
</svg>
|
||||||
Manajemen Produk
|
Manajemen Produk
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a href="/history-klasifikasi" class="flex items-center gap-3 px-4 py-3 rounded-xl transition-all {{ request()->is('history-klasifikasi*') ? 'bg-emerald-50 text-emerald-700 font-semibold' : 'text-gray-500 hover:bg-gray-50 hover:text-emerald-600' }}">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
||||||
|
</svg>
|
||||||
|
History Classification
|
||||||
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="p-4 border-t border-gray-100">
|
<div class="p-4 border-t border-gray-100">
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
use App\Http\Controllers\AuthController;
|
use App\Http\Controllers\AuthController;
|
||||||
use App\Http\Controllers\ProductController;
|
use App\Http\Controllers\ProductController;
|
||||||
use App\Http\Controllers\DatasetController;
|
use App\Http\Controllers\DatasetController;
|
||||||
|
use App\Http\Controllers\DashboardController;
|
||||||
|
use App\Http\Controllers\ClassificationHistoryAdminController;
|
||||||
|
|
||||||
// Halaman Login (GET)
|
// Halaman Login (GET)
|
||||||
Route::get('/', [AuthController::class, 'showLoginForm'])->name('login');
|
Route::get('/', [AuthController::class, 'showLoginForm'])->name('login');
|
||||||
|
|
@ -15,9 +17,10 @@
|
||||||
Route::post('/logout', [AuthController::class, 'logout'])->name('logout');
|
Route::post('/logout', [AuthController::class, 'logout'])->name('logout');
|
||||||
|
|
||||||
// Halaman Dashboard (Hanya bisa diakses jika sudah login)
|
// Halaman Dashboard (Hanya bisa diakses jika sudah login)
|
||||||
Route::get('/dashboard', function () {
|
Route::get('/dashboard', [DashboardController::class, 'index'])->middleware('auth')->name('dashboard');
|
||||||
return view('dashboard');
|
|
||||||
})->middleware('auth')->name('dashboard');
|
|
||||||
|
|
||||||
Route::resource('produk', ProductController::class);
|
Route::resource('produk', ProductController::class);
|
||||||
Route::resource('dataset', DatasetController::class);
|
Route::resource('dataset', DatasetController::class);
|
||||||
|
Route::resource('history-klasifikasi', ClassificationHistoryAdminController::class)
|
||||||
|
->only(['index', 'update', 'destroy'])
|
||||||
|
->middleware('auth');
|
||||||
Loading…
Reference in New Issue