From 6eaf12ef7b2a827e31a565d8f24cc538f3b7ade3 Mon Sep 17 00:00:00 2001 From: iFannJR Date: Wed, 3 Jun 2026 00:27:58 +0700 Subject: [PATCH] Filter Kategori Mainan --- .../Controllers/RecommendationController.php | 16 +- app/Http/Requests/RecommendationRequest.php | 1 + app/Services/ToyRecommendationService.php | 109 ++++++++++++-- resources/views/user/recommendation.blade.php | 141 ++++++++++-------- 4 files changed, 191 insertions(+), 76 deletions(-) diff --git a/app/Http/Controllers/RecommendationController.php b/app/Http/Controllers/RecommendationController.php index 6512a40..c2a8f34 100644 --- a/app/Http/Controllers/RecommendationController.php +++ b/app/Http/Controllers/RecommendationController.php @@ -6,17 +6,28 @@ use App\Models\Criteria; use App\Services\ToyRecommendationService; use Illuminate\Http\Request; +use App\Models\Product; class RecommendationController extends Controller { #untuk mengelola proses rekomendasi mainan, termasuk menampilkan form rekomendasi dan menampilkan hasil rekomendasi berdasarkan input pengguna public function __construct( protected ToyRecommendationService $sawService - ) {} + ) { + } public function index() { $criterias = Criteria::orderBy('weight_order')->get(); - return view('user.recommendation', compact('criterias')); + + $categories = Product::select('category') + ->distinct() + ->orderBy('category') + ->pluck('category'); + + return view('user.recommendation', compact( + 'criterias', + 'categories' + )); } public function result(RecommendationRequest $request) @@ -27,6 +38,7 @@ public function result(RecommendationRequest $request) 'budget_min' => $request->budget_min, 'budget_max' => $request->budget_max, 'priorities' => $request->input('priorities', []), + 'category' => $request->category, ]; $data = $this->sawService->getRecommendationsWithMatrix($input); diff --git a/app/Http/Requests/RecommendationRequest.php b/app/Http/Requests/RecommendationRequest.php index 9f56281..3d82f34 100644 --- a/app/Http/Requests/RecommendationRequest.php +++ b/app/Http/Requests/RecommendationRequest.php @@ -20,6 +20,7 @@ public function rules(): array 'budget_max' => ['required', 'numeric', 'min:0', 'gte:budget_min'], 'priorities' => ['nullable', 'array'], 'priorities.*' => ['nullable', 'numeric', 'min:1', 'max:5'], + 'category' => ['nullable', 'string'], ]; } } diff --git a/app/Services/ToyRecommendationService.php b/app/Services/ToyRecommendationService.php index 96829d8..68ceac4 100644 --- a/app/Services/ToyRecommendationService.php +++ b/app/Services/ToyRecommendationService.php @@ -14,6 +14,51 @@ class ToyRecommendationService * @param array $input ['age_min', 'age_max', 'budget_min', 'budget_max', 'priorities' => [criteria_id=>1-5, ...]] * @return Collection */ + // public function getRecommendations(array $input): Collection + // { + // $ageMin = (int) ($input['age_min'] ?? 0); + // $ageMax = (int) ($input['age_max'] ?? 99); + // $budgetMin = (float) ($input['budget_min'] ?? 0); + // $budgetMax = (float) ($input['budget_max'] ?? 999999999); + // $priorities = $input['priorities'] ?? []; + + // $products = $this->getFilteredProducts($ageMin, $ageMax, $budgetMin, $budgetMax,); + // if ($products->isEmpty()) { + // return collect(); + // } + + // $criterias = Criteria::orderBy('weight_order')->get(); + // if ($criterias->isEmpty()) { + // return $products->take(5)->map(fn($p, $i) => [ + // 'rank' => $i + 1, + // 'product' => $p, + // 'score' => 0.0, + // 'explanation' => 'Produk ini tersedia dalam rentang usia dan budget Anda.', + // ]); + // } + + // $weights = $this->buildWeightsFromPriorities($priorities, $criterias); + // $matrix = $this->buildMatrix($products, $criterias); + // $normalized = $this->normalizeMatrix($matrix, $criterias); + // $scores = $this->calculatePreferenceScores($normalized, $weights, $criterias); + + // $ranked = $scores->sortByDesc('score')->take(5)->values(); + // $criteriaNames = $criterias->keyBy('id')->map->name; + + // return $ranked->map(function ($item, $index) use ($products, $criteriaNames, $priorities) { + // $product = $products->firstWhere('id', $item['product_id']); + // if (!$product) { + // return null; + // } + // $explanation = $this->buildExplanation($product, $item['score'], $priorities, $criteriaNames); + // return [ + // 'rank' => $index + 1, + // 'product' => $product, + // 'score' => round($item['score'], 4), + // 'explanation' => $explanation, + // ]; + // })->filter()->values(); + // } public function getRecommendations(array $input): Collection { $ageMin = (int) ($input['age_min'] ?? 0); @@ -22,12 +67,22 @@ public function getRecommendations(array $input): Collection $budgetMax = (float) ($input['budget_max'] ?? 999999999); $priorities = $input['priorities'] ?? []; - $products = $this->getFilteredProducts($ageMin, $ageMax, $budgetMin, $budgetMax); + $category = $input['category'] ?? null; + + $products = $this->getFilteredProducts( + $ageMin, + $ageMax, + $budgetMin, + $budgetMax, + $category + ); + if ($products->isEmpty()) { return collect(); } $criterias = Criteria::orderBy('weight_order')->get(); + if ($criterias->isEmpty()) { return $products->take(5)->map(fn($p, $i) => [ 'rank' => $i + 1, @@ -47,10 +102,18 @@ public function getRecommendations(array $input): Collection return $ranked->map(function ($item, $index) use ($products, $criteriaNames, $priorities) { $product = $products->firstWhere('id', $item['product_id']); + if (!$product) { return null; } - $explanation = $this->buildExplanation($product, $item['score'], $priorities, $criteriaNames); + + $explanation = $this->buildExplanation( + $product, + $item['score'], + $priorities, + $criteriaNames + ); + return [ 'rank' => $index + 1, 'product' => $product, @@ -59,7 +122,6 @@ public function getRecommendations(array $input): Collection ]; })->filter()->values(); } - /** * Hitung rekomendasi SAW beserta data matriks untuk tampilan (skripsi). * @@ -73,7 +135,16 @@ public function getRecommendationsWithMatrix(array $input): array $budgetMax = (float) ($input['budget_max'] ?? 999999999); $priorities = $input['priorities'] ?? []; - $products = $this->getFilteredProducts($ageMin, $ageMax, $budgetMin, $budgetMax); + $category = $input['category'] ?? null; + + $products = $this->getFilteredProducts( + $ageMin, + $ageMax, + $budgetMin, + $budgetMax, + $category + ); + if ($products->isEmpty()) { return [ 'recommendations' => collect(), @@ -178,17 +249,35 @@ public function getRecommendationsWithMatrix(array $input): array ]; } - protected function getFilteredProducts(int $ageMin, int $ageMax, float $budgetMin, float $budgetMax): Collection - { - return Product::with('criterias') + protected function getFilteredProducts( + int $ageMin, + int $ageMax, + float $budgetMin, + float $budgetMax, + ?string $category = null +): Collection +{ + $query = Product::with('criterias') ->where('stock', '>', 0) - ->whereBetween('price', [$budgetMin, $budgetMax]) - ->get() + ->whereBetween('price', [$budgetMin, $budgetMax]); + + // Filter kategori jika dipilih + if (!empty($category)) { + $query->where('category', $category); + } + + return $query->get() ->filter(function ($product) use ($ageMin, $ageMax) { + if (empty($product->age_range)) { return true; } - return $this->ageRangeOverlaps($product->age_range, $ageMin, $ageMax); + + return $this->ageRangeOverlaps( + $product->age_range, + $ageMin, + $ageMax + ); }) ->values(); } diff --git a/resources/views/user/recommendation.blade.php b/resources/views/user/recommendation.blade.php index 26dc776..fdf1f0c 100644 --- a/resources/views/user/recommendation.blade.php +++ b/resources/views/user/recommendation.blade.php @@ -3,71 +3,84 @@ @section('title', 'Konsultasi Rekomendasi SAW') @section('content') -
-
-

Konsultasi Rekomendasi

-

Isi profil anak dan prioritas kriteria. Sistem akan merekomendasikan mainan terbaik dengan metode SAW.

-
+
+
+

Konsultasi Rekomendasi

+

Isi profil anak dan prioritas kriteria. Sistem akan merekomendasikan + mainan terbaik dengan metode SAW.

+
-
- @csrf + + @csrf - {{-- Step 1: Profil anak --}} - -

Profil anak

-

Rentang usia dan budget yang Anda inginkan.

-
- - - - -
-
- - {{-- Step 2: Prioritas kriteria --}} - -

Prioritas kriteria

-

Geser 1–5 (1 = rendah, 5 = tinggi). Nilai akan dipakai sebagai bobot SAW.

-
- @forelse(($criterias ?? collect()) as $c) - @php - $hint = $c->type === 'cost' - ? 'Cost – semakin kecil semakin baik' - : 'Benefit – semakin besar semakin baik'; - $val = old('priorities.' . $c->id, 3); - @endphp -
-
- - - {{ $val }} - ? - -
- -

{{ $hint }}

+ {{-- Step 1: Profil anak --}} + +

Profil anak

+

Rentang usia dan budget yang Anda inginkan.

+
+ + + + +
+ + + @foreach($categories as $category) + + @endforeach +
- @empty - - Kriteria belum tersedia. Tambahkan kriteria di panel admin terlebih dahulu. - - @endforelse -
-
+
+ - - Dapatkan Rekomendasi - - -
-@endsection + {{-- Step 2: Prioritas kriteria --}} + +

Prioritas kriteria

+

Geser 1–5. Nilai akan dipakai sebagai bobot + SAW.

+
+ @forelse(($criterias ?? collect()) as $c) + @php + $hint = $c->type === 'cost' + ? 'Cost – semakin kecil semakin baik' + : 'Benefit – semakin besar semakin baik'; + $val = old('priorities.' . $c->id, 3); + @endphp +
+
+ + + {{ $val }} + ? + +
+ +

{{ $hint }}

+
+ @empty + + Kriteria belum tersedia. Tambahkan kriteria di panel admin terlebih dahulu. + + @endforelse +
+
+ + + Dapatkan Rekomendasi + + +
+@endsection \ No newline at end of file