From 57faf795c57aa2a7e6af97ad2665bffebc003a85 Mon Sep 17 00:00:00 2001 From: rijalhabibullah Date: Sat, 16 May 2026 03:29:53 +0700 Subject: [PATCH] progres itulah --- .../padi_app/lib/screen/result_screen.dart | 85 +++++++-- .../lib/services/classification_service.dart | 6 +- .../lib/services/product_service.dart | 3 + .../ClassificationHistoryController.php | 10 +- .../Http/Controllers/ProductController.php | 45 ++++- web_TA/app/Models/Product.php | 3 +- ...0001_add_disease_tag_to_products_table.php | 28 +++ .../views/admin/products/edit.blade.php | 68 +++++++ .../views/admin/products/index.blade.php | 168 ++++++++++++++++-- web_TA/routes/web.php | 4 +- 10 files changed, 386 insertions(+), 34 deletions(-) create mode 100644 web_TA/database/migrations/2026_05_16_000001_add_disease_tag_to_products_table.php create mode 100644 web_TA/resources/views/admin/products/edit.blade.php diff --git a/mobile_TA/padi_app/lib/screen/result_screen.dart b/mobile_TA/padi_app/lib/screen/result_screen.dart index c341cc1..89db4af 100644 --- a/mobile_TA/padi_app/lib/screen/result_screen.dart +++ b/mobile_TA/padi_app/lib/screen/result_screen.dart @@ -233,7 +233,9 @@ class _ResultScreenState extends State { ), const SizedBox(height: 16), ...widget.result.allPredictions.entries.map((entry) { - final score = entry.value as double; + final score = (entry.value is num) + ? (entry.value as num).toDouble() + : 0.0; final percentage = (score * 100).toStringAsFixed(2); return Padding( padding: const EdgeInsets.only(bottom: 12), @@ -268,9 +270,12 @@ class _ResultScreenState extends State { ), const SizedBox(height: 30), - const Align( + Align( alignment: Alignment.centerLeft, - child: Text("Rekomendasi Produk:", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + child: Text( + "Rekomendasi Produk untuk ${_normalizeDiseaseName(widget.result.diseaseInfo.name)}:", + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), ), const SizedBox(height: 15), @@ -312,6 +317,8 @@ class _ResultScreenState extends State { } final products = snapshot.data ?? []; + final diseaseName = _normalizeDiseaseName(widget.result.diseaseInfo.name); + final filteredProducts = _filterProducts(products, diseaseName); if (products.isEmpty) { return Container( @@ -326,19 +333,24 @@ class _ResultScreenState extends State { ); } - return GridView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: 15, - mainAxisSpacing: 15, - childAspectRatio: 0.75, - ), - itemCount: products.length, - itemBuilder: (context, index) { - return _buildProductCard(products[index]); - }, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + crossAxisSpacing: 15, + mainAxisSpacing: 15, + childAspectRatio: 0.75, + ), + itemCount: filteredProducts.length, + itemBuilder: (context, index) { + return _buildProductCard(filteredProducts[index]); + }, + ), + ], ); }, ), @@ -542,6 +554,47 @@ class _ResultScreenState extends State { return buffer.toString(); } + String _normalizeDiseaseName(String diseaseName) { + final normalized = { + 'Bacterial Blight': 'Bacterial Blight', + 'bacterial blight': 'Bacterial Blight', + 'Bacterialblight': 'Bacterial Blight', + 'Bercak Bakteri (Bacterial Blight)': 'Bacterial Blight', + 'Brown Spot': 'Brown Spot', + 'brown spot': 'Brown Spot', + 'Brownspot': 'Brown Spot', + 'Bercak Coklat (Brown Spot)': 'Brown Spot', + 'Leaf Smut': 'Leaf Smut', + 'leaf smut': 'Leaf Smut', + 'Leafsmut': 'Leaf Smut', + 'Jamur Daun (Leaf Smut)': 'Leaf Smut', + 'Healthy': 'Healthy', + 'healthy': 'Healthy', + 'Sehat (Healthy)': 'Healthy', + }; + + return normalized[diseaseName] ?? diseaseName; + } + + List _filterProducts( + List products, + String disease, + ) { + if (disease == 'Healthy') { + return products; + } + + final tagged = products.where((product) { + final tag = product.diseaseTag; + if (tag == null || tag.trim().isEmpty) { + return false; + } + return _normalizeDiseaseName(tag) == disease; + }).toList(); + + return tagged; + } + void _showDetailPopup(BuildContext context) { showDialog( context: context, diff --git a/mobile_TA/padi_app/lib/services/classification_service.dart b/mobile_TA/padi_app/lib/services/classification_service.dart index 0bb731d..757862b 100644 --- a/mobile_TA/padi_app/lib/services/classification_service.dart +++ b/mobile_TA/padi_app/lib/services/classification_service.dart @@ -210,7 +210,7 @@ class ClassificationService { final response = await _httpClient .get( - Uri.parse('$_historyUrl?page=$page$userParam'), + Uri.parse('$_historyUrl?page=$page&per_page=1000$userParam'), headers: {'Accept': 'application/json'}, ) .timeout(const Duration(seconds: 15)); @@ -278,7 +278,9 @@ class ClassificationResult { return ClassificationResult( predictedClass: json['predicted_class'] ?? '', confidence: json['confidence'] ?? '0%', - confidenceValue: json['confidence_value'] ?? 0.0, + confidenceValue: (json['confidence_value'] is num) + ? (json['confidence_value'] as num).toDouble() + : 0.0, allPredictions: Map.from( json['all_predictions'] ?? {}, ), diff --git a/mobile_TA/padi_app/lib/services/product_service.dart b/mobile_TA/padi_app/lib/services/product_service.dart index 3c96261..45419d1 100644 --- a/mobile_TA/padi_app/lib/services/product_service.dart +++ b/mobile_TA/padi_app/lib/services/product_service.dart @@ -9,6 +9,7 @@ class ProductItem { final double price; final String? imageUrl; final String? marketplaceLink; + final String? diseaseTag; ProductItem({ required this.id, @@ -17,6 +18,7 @@ class ProductItem { this.description, this.imageUrl, this.marketplaceLink, + this.diseaseTag, }); factory ProductItem.fromJson(Map json) { @@ -35,6 +37,7 @@ class ProductItem { price: priceValue, imageUrl: json['image_url'], marketplaceLink: json['marketplace_link'], + diseaseTag: json['disease_tag'], ); } } diff --git a/web_TA/app/Http/Controllers/ClassificationHistoryController.php b/web_TA/app/Http/Controllers/ClassificationHistoryController.php index f3d4ff4..68059ab 100644 --- a/web_TA/app/Http/Controllers/ClassificationHistoryController.php +++ b/web_TA/app/Http/Controllers/ClassificationHistoryController.php @@ -19,7 +19,15 @@ public function index(Request $request) $query->where('user_id', (int) $request->input('user_id')); } - $classifications = $query->paginate(15); + $perPage = (int) $request->input('per_page', 15); + if ($perPage < 1) { + $perPage = 15; + } + if ($perPage > 1000) { + $perPage = 1000; + } + + $classifications = $query->paginate($perPage); return response()->json([ 'success' => true, diff --git a/web_TA/app/Http/Controllers/ProductController.php b/web_TA/app/Http/Controllers/ProductController.php index 6741379..2468ab1 100644 --- a/web_TA/app/Http/Controllers/ProductController.php +++ b/web_TA/app/Http/Controllers/ProductController.php @@ -12,12 +12,51 @@ public function index() { return view('admin.products.index', compact('products')); } + public function edit(Product $product) { + return view('admin.products.edit', compact('product')); + } + + public function update(Request $request, Product $product) { + $request->validate([ + 'name' => 'required', + 'price' => 'required|numeric', + 'image' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', + 'marketplace_link' => 'required|url|max:2048', + 'disease_tag' => 'nullable|string|max:50' + ]); + + $imagePath = $product->image; + if ($request->hasFile('image')) { + $newPath = $this->compressAndStoreProductImage($request->file('image')) + ?? $request->file('image')->store('products', 'public'); + + if ($newPath) { + if ($imagePath) { + Storage::disk('public')->delete($imagePath); + } + $imagePath = $newPath; + } + } + + $product->update([ + 'name' => $request->name, + 'description' => $request->description, + 'price' => $request->price, + 'image' => $imagePath, + 'marketplace_link' => $request->marketplace_link, + 'disease_tag' => $request->disease_tag ?: null + ]); + + return redirect()->route('produk.index')->with('success', 'Produk berhasil diperbarui!'); + } + public function store(Request $request) { $request->validate([ 'name' => 'required', 'price' => 'required|numeric', 'image' => 'image|mimes:jpeg,png,jpg|max:2048', - 'marketplace_link' => 'required|url|max:2048' + 'marketplace_link' => 'required|url|max:2048', + 'disease_tag' => 'nullable|string|max:50' ]); $imagePath = null; @@ -31,7 +70,8 @@ public function store(Request $request) { 'description' => $request->description, 'price' => $request->price, 'image' => $imagePath, - 'marketplace_link' => $request->marketplace_link + 'marketplace_link' => $request->marketplace_link, + 'disease_tag' => $request->disease_tag ?: null ]); return back()->with('success', 'Produk berhasil ditambahkan!'); @@ -55,6 +95,7 @@ public function apiIndex(Request $request) { 'price' => $product->price, 'image_url' => $imageUrl, 'marketplace_link' => $product->marketplace_link, + 'disease_tag' => $product->disease_tag, ]; }); diff --git a/web_TA/app/Models/Product.php b/web_TA/app/Models/Product.php index 59c2239..26a7122 100644 --- a/web_TA/app/Models/Product.php +++ b/web_TA/app/Models/Product.php @@ -15,6 +15,7 @@ class Product extends Model 'description', 'price', 'image', - 'marketplace_link' + 'marketplace_link', + 'disease_tag' ]; } \ No newline at end of file diff --git a/web_TA/database/migrations/2026_05_16_000001_add_disease_tag_to_products_table.php b/web_TA/database/migrations/2026_05_16_000001_add_disease_tag_to_products_table.php new file mode 100644 index 0000000..ad2d97f --- /dev/null +++ b/web_TA/database/migrations/2026_05_16_000001_add_disease_tag_to_products_table.php @@ -0,0 +1,28 @@ +string('disease_tag', 50)->nullable()->after('marketplace_link'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('products', function (Blueprint $table) { + $table->dropColumn('disease_tag'); + }); + } +}; diff --git a/web_TA/resources/views/admin/products/edit.blade.php b/web_TA/resources/views/admin/products/edit.blade.php new file mode 100644 index 0000000..ae91019 --- /dev/null +++ b/web_TA/resources/views/admin/products/edit.blade.php @@ -0,0 +1,68 @@ +@extends('layouts.admin') +@section('title', 'Edit Produk') + +@section('content') + +@if(session('success')) +
+ {{ session('success') }} +
+@endif + +@if($errors->any()) +
+
    + @foreach($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+@endif + +
+
+

Edit Produk

+
+ @csrf + @method('PUT') + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +

Kosongkan jika tidak ingin mengganti.

+ @if($product->image) +
+ + Foto saat ini +
+ @endif +
+ +
+ + Batal +
+
+
+
+@endsection diff --git a/web_TA/resources/views/admin/products/index.blade.php b/web_TA/resources/views/admin/products/index.blade.php index ad48d16..bd954ec 100644 --- a/web_TA/resources/views/admin/products/index.blade.php +++ b/web_TA/resources/views/admin/products/index.blade.php @@ -46,6 +46,17 @@ +
+ + +

Kosongkan jika produk untuk umum.

+
+
@@ -59,18 +70,18 @@
- +
- - - + + + @foreach($products as $product) - @endforeach @@ -98,4 +128,120 @@ + + + + @endsection \ No newline at end of file diff --git a/web_TA/routes/web.php b/web_TA/routes/web.php index a96e232..f60dc73 100644 --- a/web_TA/routes/web.php +++ b/web_TA/routes/web.php @@ -19,7 +19,9 @@ // Halaman Dashboard (Hanya bisa diakses jika sudah login) Route::get('/dashboard', [DashboardController::class, 'index'])->middleware('auth')->name('dashboard'); -Route::resource('produk', ProductController::class); +Route::resource('produk', ProductController::class)->parameters([ + 'produk' => 'product' +]); Route::resource('dataset', DatasetController::class); Route::resource('history-klasifikasi', ClassificationHistoryAdminController::class) ->only(['index', 'update', 'destroy'])
Info ProdukHargaAksiInfo ProdukHargaAksi
+
@if($product->image) @@ -79,17 +90,36 @@
📦
@endif
-
-
{{ $product->name }}
+
+
{{ $product->name }}
+
+ {{ $product->disease_tag ?: 'Tidak terkait' }} +
Rp {{ number_format($product->price) }} -
- @csrf @method('DELETE') - -
+
+ +
+ @csrf @method('DELETE') + +
+