diff --git a/app/Http/Controllers/Landingpage/HomeController.php b/app/Http/Controllers/Landingpage/HomeController.php index 7a9cca1..1638b1d 100644 --- a/app/Http/Controllers/Landingpage/HomeController.php +++ b/app/Http/Controllers/Landingpage/HomeController.php @@ -15,6 +15,90 @@ class HomeController extends Controller { public function index() { - // + // Ambil semua sub kriteria dan relasinya dengan kriteria + $subKriteria = SubKriteria::with('kriteria')->get()->groupBy(function ($item) { + return $item->kriteria->nama_kriteria; + }); + + return view('landingpage.master', [ + 'subKriteria' => $subKriteria + ]); } + public function prosesRekomendasi(Request $request) +{ + $sub_kriterias = $request->input('sub_kriteria'); // [kriteria_id => sub_kriteria_id] + + if (!$sub_kriterias) { + return back()->with('error', 'Harap pilih semua preferensi terlebih dahulu.'); + } + + $selectedSubs = SubKriteria::whereIn('id', array_values($sub_kriterias))->get()->keyBy('id'); + $allPakaian = Pakaian::with('subKriterias')->get(); + + // Step 1: Filter berdasarkan preferensi user + $filteredPakaian = $allPakaian->filter(function ($pakaian) use ($sub_kriterias, $selectedSubs) { + foreach ($sub_kriterias as $kriteria_id => $sub_id) { + if ($kriteria_id == 2) { // Harga (range) + $harga = $pakaian->harga; + $sub = $selectedSubs[$sub_id]; + if ($harga < $sub->min_harga || $harga > $sub->max_harga) { + return false; + } + } elseif ($kriteria_id == 3) { // Jenis Pakaian (C3) WAJIB SAMA + $match = $pakaian->subKriterias->contains(function ($item) use ($kriteria_id, $sub_id) { + return $item->kriteria_id == $kriteria_id && $item->id == $sub_id; + }); + if (!$match) return false; + } else { + // Untuk kriteria lain, cukup memiliki salah satu sub_kriteria + $match = $pakaian->subKriterias->where('kriteria_id', $kriteria_id)->isNotEmpty(); + if (!$match) return false; + } + } + return true; + }); + + if ($filteredPakaian->isEmpty()) { + return back()->with('error', 'Tidak ada pakaian yang sesuai dengan preferensi Anda.'); + } + + // Step 2: Siapkan bobot & maksimum nilai + $kriterias = Kriteria::all()->keyBy('id'); + $maxPerKriteria = []; + foreach ($kriterias as $kriteria_id => $kriteria) { + $maxPerKriteria[$kriteria_id] = SubKriteria::where('kriteria_id', $kriteria_id)->max('nilai') ?: 1; + } + + // Step 3: Hitung skor SAW + $matrix = []; + + foreach ($filteredPakaian as $pakaian) { + $score = 0; + + foreach ($kriterias as $kriteria_id => $kriteria) { + $bobot = $kriteria->bobot; + $jenis = $kriteria->jenis; + + // Ambil sub_kriteria tertinggi untuk kriteria ini + $subs = $pakaian->subKriterias->where('kriteria_id', $kriteria_id); + $sub = $subs->sortByDesc('nilai')->first(); + + if ($sub) { + $nilai = $sub->nilai; + $max = $maxPerKriteria[$kriteria_id]; + $normal = $jenis == 'COST' ? ($nilai ? $max / $nilai : 0) : $nilai / $max; + $score += $normal * $bobot; + } + } + + $matrix[] = [ + 'pakaian' => $pakaian, + 'score' => round($score, 3), + ]; + } + + $rekomendasi = collect($matrix)->sortByDesc('score')->values(); + return view('landingpage.hasil', compact('rekomendasi')); +} + } diff --git a/app/Http/Controllers/PakaianController.php b/app/Http/Controllers/PakaianController.php index d81b67f..3073910 100644 --- a/app/Http/Controllers/PakaianController.php +++ b/app/Http/Controllers/PakaianController.php @@ -2,9 +2,12 @@ namespace App\Http\Controllers; -use App\Models\Kriteria; use App\Models\Pakaian; +use App\Models\Kriteria; +use App\Models\SubKriteria; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Storage; class PakaianController extends Controller { @@ -31,11 +34,14 @@ class PakaianController extends Controller /** * Show the form for creating a new resource. */ + public function create() { - // + $kriterias = Kriteria::with('subKriteria')->get(); // load kriteria + sub_kriterias + return view('admin.pages.pakaian.create', compact('kriterias')); } + /** * Store a newly created resource in storage. */ @@ -45,23 +51,44 @@ class PakaianController extends Controller 'nama_pakaian' => 'required|string|max:255', 'harga' => 'required|numeric', 'img' => 'nullable|image|max:2048', + 'sub_kriterias' => 'array|nullable', ]); - $imgPath = null; - if ($request->hasFile('img')) { - $imgPath = $request->file('img')->store('uploads/pakaian', 'public'); + DB::beginTransaction(); + + try { + // Upload image jika ada + $path = null; + if ($request->hasFile('img')) { + $path = $request->file('img')->store('pakaian', 'public'); + } + + // Simpan pakaian + $pakaian = Pakaian::create([ + 'nama_pakaian' => $request->nama_pakaian, + 'harga' => $request->harga, + 'img' => $path, + ]); + + // Simpan relasi sub kriteria + if ($request->filled('sub_kriterias')) { + $pakaian->subKriterias()->sync($request->sub_kriterias); + } + + DB::commit(); + + return redirect()->route('admin.pakaian.index')->with('success', 'Pakaian berhasil ditambahkan.'); + } catch (\Exception $e) { + // Rollback semua jika ada error + DB::rollBack(); + + // Hapus file yang sudah di-upload jika perlu + if ($path && Storage::disk('public')->exists($path)) { + Storage::disk('public')->delete($path); + } + + return redirect()->back()->with('error', 'Gagal menambahkan pakaian: ' . $e->getMessage()); } - - $pakaian = Pakaian::create([ - 'nama_pakaian' => $request->nama_pakaian, - 'harga' => $request->harga, - 'img' => $imgPath ? 'storage/' . $imgPath : null, - ]); - - // Simpan relasi subkriteria (many-to-many) - $pakaian->subKriterias()->sync($request->subkriterias); - - return redirect()->route('admin.pakaian.index')->with('success', 'Data pakaian berhasil ditambahkan.'); } @@ -76,42 +103,58 @@ class PakaianController extends Controller /** * Show the form for editing the specified resource. */ - public function edit(string $id) + public function edit($id) { - // + $pakaian = Pakaian::with('subKriterias')->findOrFail($id); + $kriterias = Kriteria::with('subKriteria')->get(); + + return view('admin.pages.pakaian.edit', compact('pakaian', 'kriterias')); } - /** - * Update the specified resource in storage. - */ public function update(Request $request, $id) { $request->validate([ 'nama_pakaian' => 'required|string|max:255', - 'harga' => 'required|numeric|min:0', - 'gambar' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', + 'harga' => 'required|numeric', + 'img' => 'nullable|image|max:2048', + 'sub_kriterias' => 'array|nullable', ]); - $pakaian = Pakaian::findOrFail($id); - $pakaian->nama_pakaian = $request->nama_pakaian; - $pakaian->harga = $request->harga; + DB::beginTransaction(); - // Jika upload gambar baru - if ($request->hasFile('gambar')) { - if ($pakaian->img && file_exists(public_path($pakaian->img))) { - unlink(public_path($pakaian->img)); + try { + $pakaian = Pakaian::findOrFail($id); + $path = $pakaian->img; + + // Update gambar jika ada file baru + if ($request->hasFile('img')) { + if ($path && Storage::disk('public')->exists($path)) { + Storage::disk('public')->delete($path); + } + $path = $request->file('img')->store('pakaian', 'public'); } - $file = $request->file('gambar'); - $path = 'uploads/pakaian/'; - $filename = time() . '_' . $file->getClientOriginalName(); - $file->move(public_path($path), $filename); - $pakaian->img = $path . $filename; + $pakaian->update([ + 'nama_pakaian' => $request->nama_pakaian, + 'harga' => $request->harga, + 'img' => $path, + ]); + + // Update relasi sub_kriterias + $pakaian->subKriterias()->sync($request->sub_kriterias ?? []); + + DB::commit(); + + return redirect()->route('admin.pakaian.index')->with('success', 'Pakaian berhasil diperbarui.'); + } catch (\Exception $e) { + DB::rollBack(); + + if (isset($path) && $request->hasFile('img') && Storage::disk('public')->exists($path)) { + Storage::disk('public')->delete($path); + } + + return redirect()->back()->with('error', 'Gagal memperbarui pakaian: ' . $e->getMessage()); } - - $pakaian->save(); - - return redirect()->back()->with('success', 'Data pakaian berhasil diperbarui!'); } diff --git a/database/migrations/2025_05_24_133426_create_pakaian_sub_kriterias_table.php b/database/migrations/2025_05_24_133426_create_pakaian_sub_kriterias_table.php index 1bb02cd..04292af 100644 --- a/database/migrations/2025_05_24_133426_create_pakaian_sub_kriterias_table.php +++ b/database/migrations/2025_05_24_133426_create_pakaian_sub_kriterias_table.php @@ -11,14 +11,13 @@ return new class extends Migration */ public function up(): void { - Schema::create('pakaian_sub_kriterias', function (Blueprint $table) { - $table->id(); - $table->foreignId('pakaian_id')->constrained()->onDelete('cascade'); - $table->foreignId('sub_kriteria_id')->constrained()->onDelete('cascade'); - $table->timestamps(); - - $table->unique(['pakaian_id', 'sub_kriteria_id']); - }); + Schema::create('pakaian_sub_kriterias', function (Blueprint $table) { + $table->id(); + $table->foreignId('pakaian_id')->constrained()->onDelete('cascade'); + $table->foreignId('sub_kriteria_id')->constrained()->onDelete('cascade'); + $table->timestamps(); + $table->unique(['pakaian_id', 'sub_kriteria_id']); + }); } /** diff --git a/resources/views/admin/layouts/base.blade.php b/resources/views/admin/layouts/base.blade.php index 28c6467..38e9c27 100644 --- a/resources/views/admin/layouts/base.blade.php +++ b/resources/views/admin/layouts/base.blade.php @@ -25,9 +25,10 @@ @@ -92,33 +92,35 @@ + + @endpush + @stack('scripts') + + + + + +
+ + Batal + + +
+ + + + + +@endsection diff --git a/resources/views/admin/pages/pakaian/edit.blade.php b/resources/views/admin/pages/pakaian/edit.blade.php new file mode 100644 index 0000000..4c3acec --- /dev/null +++ b/resources/views/admin/pages/pakaian/edit.blade.php @@ -0,0 +1,107 @@ +@extends('admin.layouts.base') + +@section('title', 'Edit Pakaian') + +@section('content') +
+
+
+ +
Edit Pakaian
+
+ +
+
+ @csrf + @method('PUT') + +
+ + +
+ +
+ +
+ Rp + +
+
+ +
+
+ @if ($pakaian->img && Storage::disk('public')->exists($pakaian->img)) + Gambar Lama + @else +

Tidak ada gambar

+ @endif +
+ +
+ + +
+ +
+ +
+ @foreach ($kriterias as $kriteria) +
+
{{ $kriteria->nama_kriteria }}
+ @foreach ($kriteria->subKriteria as $sub) +
+ id, $pakaian->subKriterias->pluck('id')->toArray()) ? 'checked' : '' }}> + +
+ @endforeach +
+ @endforeach +
+
+ + @push('scripts') + + @endpush + @stack('scripts') + +
+ + Batal + + +
+
+
+
+
+@endsection diff --git a/resources/views/admin/pages/pakaian/index.blade.php b/resources/views/admin/pages/pakaian/index.blade.php index c68d2be..73371af 100644 --- a/resources/views/admin/pages/pakaian/index.blade.php +++ b/resources/views/admin/pages/pakaian/index.blade.php @@ -7,49 +7,50 @@ @if (session('success')) + .pagination .page-item.active .page-link { + background-color: #053B2D; + /* Biru lebih gelap */ + font-weight: bold; + } - + + + -@endsection + @endsection diff --git a/resources/views/landingpage/hasil.blade.php b/resources/views/landingpage/hasil.blade.php new file mode 100644 index 0000000..bdddd4d --- /dev/null +++ b/resources/views/landingpage/hasil.blade.php @@ -0,0 +1,69 @@ + + + + + + Hasil Rekomendasi + + + + + + + +
+

Hasil Rekomendasi Pakaian

+ + @if ($rekomendasi->isEmpty()) +
+ Tidak ada pakaian yang cocok dengan preferensimu. +
+ @else + @foreach ($rekomendasi as $item) +
+
+
+ Gambar {{ $item['pakaian']->nama_pakaian }} +
+
+
+
{{ $item['pakaian']->nama_pakaian }}
+

Harga: Rp + {{ number_format($item['pakaian']->harga, 0, ',', '.') }}

+

Skor Kecocokan: {{ number_format($item['score'], 3) }}

+
+
+
+
+ @endforeach + @endif +
+ + + + + diff --git a/resources/views/landingpage/pilihpakaian.blade.php b/resources/views/landingpage/pilihpakaian.blade.php index c592ffe..9d36f45 100644 --- a/resources/views/landingpage/pilihpakaian.blade.php +++ b/resources/views/landingpage/pilihpakaian.blade.php @@ -1,147 +1,65 @@
-
-
-
+ @csrf
Pilih Pakaian Anda
-

Untuk mendapatkan rekomendasi pakaian yang paling sesuai dengan gaya dan kebutuhan Anda, silakan isi beberapa pertanyaan berikut.

+

Untuk mendapatkan rekomendasi pakaian yang sesuai dengan preferensi Anda. +

+
-
+
- -
-
Pilih jenis acara yang akan kamu hadiri...
-
- - -
-
- - -
-
- - -
- -
+ @php + $steps = [ + 'Jenis Pakaian' => $subKriteria['Jenis Pakaian'] ?? [], + 'Harga' => $subKriteria['Harga'] ?? [], + 'Jenis Acara' => $subKriteria['Jenis Acara'] ?? [], + 'Warna Pakaian' => $subKriteria['Warna Pakaian'] ?? [], + 'Cuaca Acara' => $subKriteria['Cuaca Acara'] ?? [], + 'Lokasi Acara' => $subKriteria['Lokasi Acara'] ?? [], + ]; + $stepIndex = 1; + @endphp - -
-
Tentukan rentang harga pakaian...
-
- - -
-
- - -
-
- - -
-
- - -
- - -
+ @foreach ($steps as $label => $subs) +
+
Pilih {{ strtolower($label) }}...
+ @foreach ($subs as $sub) +
+ + +
+ @endforeach - -
-
Pilih jenis pakaian...
-
- - +
+ @if ($stepIndex > 1) + + @endif + @if ($stepIndex < count($steps)) + + @else + + @endif +
-
- - -
-
- - -
-
- - -
-
- - -
- - -
- - -
-
Pilih warna pakaian...
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- - -
- - -
-
Pilih kondisi cuaca...
-
- - -
-
- - -
- - -
- - -
-
Pilih lokasi acara...
-
- - -
-
- - -
- - -
+ @php $stepIndex++; @endphp + @endforeach
@@ -150,9 +68,10 @@
+ - -
diff --git a/routes/web.php b/routes/web.php index 04c56e2..e3fe5ba 100644 --- a/routes/web.php +++ b/routes/web.php @@ -27,17 +27,9 @@ use App\Http\Controllers\Admin\ResetPasswordController; Route::get('/', [HomeController::class, 'index'])->name('home'); -Route::post('/proses-rekomendasi', [HomeController::class, 'simpankuisionerdanrekomendasi'])->name('proses.rekomendasi'); +Route::post('/proses-rekomendasi', [HomeController::class, 'prosesRekomendasi'])->name('proses.rekomendasi'); -// // Jika user sudah login, arahkan ke dashboard -// Route::get('/dashboard', function () { -// if (!Auth::check()) { -// return redirect()->route('login'); -// } -// return view('admin.pages.dashboard.index'); -// })->name('dashboard'); - // Login & Register Routes Route::get('/login', [AuthController::class, 'showLogin'])->name('login'); Route::post('/login', [AuthController::class, 'login']); @@ -66,8 +58,8 @@ Route::prefix('admin')->middleware(['auth'])->as('admin.')->group(function () { Route::resource('kriteria', KriteriaController::class); Route::resource('subkriteria', SubkriteriaController::class); Route::get('kriteria/subkriteria/{nama_kriteria}', [SubKriteriaShowController::class, 'indexShow'])->name('kriteria.subkriteria.index'); - Route::resource('pakaian', PakaianController::class); + Route::resource('penilaian', PenilaianController::class); Route::resource('user', UserController::class); });