route('perbandingan') ->with('error', 'Selesaikan perhitungan bobot kriteria terlebih dahulu.'); } $komponens = Komponen::all(); $waktuMakans = WaktuMakan::all(); // Get filter parameters $waktuMakanId = $request->waktu_makan_id; $search = $request->q; // Get expert recommendations and available foods $rekomendasiAhli = collect(); $makanans = collect(); if ($waktuMakanId) { // Get expert recommendations for all components with eager loading $rekomendasiAhli = RekomendasiAhli::with(['makanan', 'komponen', 'waktuMakan']) ->where('waktu_makan_id', $waktuMakanId) ->get(); // Get all foods from pivot table makanan_komponen_waktu $query = MakananKomponenWaktu::with(['makanan', 'komponen']) ->where('waktu_makan_id', $waktuMakanId); // Try with status filter first $makananKomponenWaktu = $query->where('status', true)->get(); // If no data with status true, try without status filter if ($makananKomponenWaktu->isEmpty()) { $makananKomponenWaktu = $query->get(); \Log::info('No data with status=true, using all data without status filter'); } if ($search) { $makananKomponenWaktu = $makananKomponenWaktu->filter(function($item) use ($search) { return stripos($item->makanan->nama, $search) !== false; }); } // Debug: Check all data without status filter $allData = MakananKomponenWaktu::with(['makanan', 'komponen']) ->where('waktu_makan_id', $waktuMakanId) ->get(); \Log::info('Debug - All data without status filter:', [ 'waktu_makan_id' => $waktuMakanId, 'total_all_data' => $allData->count(), 'status_counts' => $allData->groupBy('status')->map->count(), 'all_data' => $allData->toArray() ]); // Log untuk debugging \Log::info('Data makanan dari pivot table:', [ 'waktu_makan_id' => $waktuMakanId, 'total_makanan' => $makananKomponenWaktu->count(), 'makanan_data' => $makananKomponenWaktu->toArray() ]); // Transform data to match expected format $makanans = $makananKomponenWaktu->map(function($item) { $makanan = $item->makanan; $makanan->komponen_id = $item->komponen_id; return $makanan; }); } return view('admin.alternatif.pilih', compact( 'rekomendasiAhli', 'makanans', 'komponens', 'waktuMakans', 'waktuMakanId', 'search' )); } // 2. Simpan pilihan alternatif dan hitung bobot public function pilihAlternatif(Request $request) { try { $alternatifIds = $request->input('alternatifs'); $waktuMakanId = $request->input('waktu_makan_id'); $komponenIds = $request->input('komponen_ids'); $tanggalRekomendasi = now()->toDateString(); if (!$alternatifIds || !$waktuMakanId || !$komponenIds) { return redirect()->back() ->with('error', 'Pilih minimal 4 alternatif untuk setiap komponen.'); } // Validasi minimal 4 alternatif per komponen $makananKomponenWaktu = MakananKomponenWaktu::with(['makanan', 'komponen']) ->whereIn('makanan_id', $alternatifIds) ->where('waktu_makan_id', $waktuMakanId) ->get(); // Group by komponen dan cek jumlah $alternatifPerKomponen = $makananKomponenWaktu->groupBy('komponen_id'); $errorMessages = []; foreach ($alternatifPerKomponen as $komponenId => $items) { if ($items->count() < 4) { $komponen = $items->first()->komponen; $errorMessages[] = "Komponen {$komponen->nama}: hanya {$items->count()} alternatif (minimal 4)"; } } if (!empty($errorMessages)) { return redirect()->back() ->with('error', 'Validasi gagal:
' . implode('
', $errorMessages)); } // Log request data untuk debugging \Log::info('Request data:', [ 'alternatifIds' => $alternatifIds, 'waktuMakanId' => $waktuMakanId, 'komponenIds' => $komponenIds, 'all_request' => $request->all() ]); // Ambil data makanan yang dipilih dari pivot table $makananKomponenWaktu = MakananKomponenWaktu::with(['makanan']) ->whereIn('makanan_id', $alternatifIds) ->where('waktu_makan_id', $waktuMakanId) ->where('status', true) ->get(); // If no data with status true, try without status filter if ($makananKomponenWaktu->isEmpty()) { $makananKomponenWaktu = MakananKomponenWaktu::with(['makanan']) ->whereIn('makanan_id', $alternatifIds) ->where('waktu_makan_id', $waktuMakanId) ->get(); \Log::info('No data with status=true in pilihAlternatif, using all data without status filter'); } // Group makanan berdasarkan komponen $alternatifsByKomponen = []; foreach ($makananKomponenWaktu as $item) { $makanan = $item->makanan; $komponenId = $item->komponen_id; if (!isset($alternatifsByKomponen[$komponenId])) { $komponen = Komponen::find($komponenId); $alternatifsByKomponen[$komponenId] = [ 'nama_komponen' => $komponen->nama, 'alternatifs' => [] ]; } // Simpan data makanan dalam format array $alternatifsByKomponen[$komponenId]['alternatifs'][] = [ 'id' => $makanan->id, 'nama' => $makanan->nama, 'energi' => $makanan->energi, 'lemak' => $makanan->lemak, 'karbohidrat' => $makanan->karbohidrat, 'natrium' => $makanan->natrium, 'lemak_invers' => $makanan->lemak > 0 ? 1 / $makanan->lemak : 0, 'natrium_invers' => $makanan->natrium > 0 ? 1 / $makanan->natrium : 0 ]; } // Log data untuk debugging \Log::info('Data alternatif yang akan disimpan:', [ 'alternatifs_by_komponen' => $alternatifsByKomponen, 'waktu_makan_id' => $waktuMakanId, 'tanggal_rekomendasi' => $tanggalRekomendasi ]); // Simpan ke session session([ 'alternatifs_by_komponen' => $alternatifsByKomponen, 'waktu_makan_id' => $waktuMakanId, 'hasil_rekomendasi' => [ 'waktu_makan_id' => $waktuMakanId, 'tanggal_rekomendasi' => $tanggalRekomendasi, 'waktu_makan_nama' => WaktuMakan::find($waktuMakanId)->nama, 'alternatifs_dipilih' => $alternatifIds ] ]); return redirect()->route('alternatif.view') ->with('success', 'Alternatif berhasil dipilih.'); } catch (\Exception $e) { \Log::error('Error in simpanPilihan:', [ 'message' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return redirect()->back() ->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); } } public function viewAlternatif() { try { // Ambil data dari session $alternatifsByKomponen = session('alternatifs_by_komponen'); $hasilRekomendasi = session('hasil_rekomendasi'); if (!$hasilRekomendasi || !$alternatifsByKomponen) { return redirect()->route('alternatif.pilih') ->with('error', 'Silakan pilih alternatif terlebih dahulu'); } // Log data untuk debugging \Log::info('Data untuk view:', [ 'alternatifs_by_komponen' => $alternatifsByKomponen, 'hasil_rekomendasi' => $hasilRekomendasi ]); return view('admin.alternatif.view', compact('alternatifsByKomponen', 'hasilRekomendasi')); } catch (\Exception $e) { \Log::error('Error in viewAlternatif:', [ 'message' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return redirect()->route('alternatif.pilih') ->with('error', 'Terjadi kesalahan saat memproses data: ' . $e->getMessage()); } } // 3. Hitung bobot alternatif private function hitungBobotAlternatif($alternativesByComponent, $kriterias, $waktuMakanId) { try { // Random Index values $RI = [ 1 => 0.00, 2 => 0.00, 3 => 0.58, 4 => 0.90, 5 => 1.12, 6 => 1.24, 7 => 1.32, 8 => 1.41, 9 => 1.45, 10 => 1.49, 11 => 1.51, 12 => 1.48, 13 => 1.56, 14 => 1.57, 15 => 1.59 ]; // Ambil bobot kriteria berdasarkan waktu makan $bobotKriteria = []; foreach ($kriterias as $kriteria) { $bobot = BobotKriteria::where([ 'kriteria_id' => $kriteria->id, 'waktu_makan_id' => $waktuMakanId ])->first(); if (!$bobot) { throw new \Exception("Bobot kriteria untuk {$kriteria->nama} pada waktu makan ini belum dihitung."); } $bobotKriteria[$kriteria->id] = $bobot->bobot; } foreach ($alternativesByComponent as $currentKomponenId => $komponenAlternatifs) { // Untuk setiap kriteria foreach ($kriterias as $kriteria) { // 1. Buat matriks perbandingan berpasangan untuk komponen ini $matriksPerbandingan = []; // Isi matriks perbandingan foreach ($komponenAlternatifs as $alt1) { foreach ($komponenAlternatifs as $alt2) { $nilai1 = $alt1->{strtolower($kriteria->nama)}; $nilai2 = $alt2->{strtolower($kriteria->nama)}; // Untuk kriteria cost (lemak dan natrium), gunakan nilai inverse if ($this->isCostCriteria($kriteria->nama)) { $nilai1 = $nilai1 > 0 ? 1 / $nilai1 : 0; $nilai2 = $nilai2 > 0 ? 1 / $nilai2 : 0; } if ($nilai2 == 0) { $matriksPerbandingan[$alt1->id][$alt2->id] = 0; } else { $matriksPerbandingan[$alt1->id][$alt2->id] = $nilai1 / $nilai2; } // Simpan perbandingan ke database PerbandinganAlternatif::updateOrCreate( [ 'alternatif_id_1' => $alt1->id, 'alternatif_id_2' => $alt2->id, 'kriteria_id' => $kriteria->id, 'waktu_makan_id' => $waktuMakanId, 'komponen_id' => $currentKomponenId ], ['nilai' => $matriksPerbandingan[$alt1->id][$alt2->id]] ); } } // 2. Hitung jumlah kolom $jumlahKolom = []; foreach ($komponenAlternatifs as $alt2) { $jumlahKolom[$alt2->id] = 0; foreach ($komponenAlternatifs as $alt1) { $jumlahKolom[$alt2->id] += $matriksPerbandingan[$alt1->id][$alt2->id]; } } // 3. Normalisasi matriks $matriksNormal = []; foreach ($komponenAlternatifs as $alt1) { foreach ($komponenAlternatifs as $alt2) { if ($jumlahKolom[$alt2->id] > 0) { $matriksNormal[$alt1->id][$alt2->id] = $matriksPerbandingan[$alt1->id][$alt2->id] / $jumlahKolom[$alt2->id]; } else { $matriksNormal[$alt1->id][$alt2->id] = 0; } } } // 4. Hitung prioritas lokal $priorityVector = []; foreach ($komponenAlternatifs as $alt) { $priorityVector[$alt->id] = array_sum($matriksNormal[$alt->id]) / count($komponenAlternatifs); } // 5. Hitung CI dan CR $lambdaMax = 0; foreach ($komponenAlternatifs as $alt) { $sum = 0; foreach ($komponenAlternatifs as $alt2) { $sum += $matriksPerbandingan[$alt->id][$alt2->id] * $priorityVector[$alt2->id]; } if ($priorityVector[$alt->id] > 0) { $lambdaMax += $sum / $priorityVector[$alt->id]; } } $lambdaMax = $lambdaMax / count($komponenAlternatifs); $CI = ($lambdaMax - count($komponenAlternatifs)) / (count($komponenAlternatifs) - 1); $CR = $CI / ($RI[count($komponenAlternatifs)] ?? 1.59); // Simpan CR ke database ConsistencyRatioAlternatif::updateOrCreate( [ 'kriteria_id' => $kriteria->id, 'waktu_makan_id' => $waktuMakanId, 'komponen_id' => $currentKomponenId ], [ 'ci' => $CI, 'cr' => $CR, 'is_consistent' => $CR <= 0.1 ] ); // Simpan skor akhir ke database foreach ($komponenAlternatifs as $alt) { SkorMakanan::updateOrCreate( [ 'makanan_id' => $alt->id, 'kriteria_id' => $kriteria->id, 'waktu_makan_id' => $waktuMakanId, 'komponen_id' => $currentKomponenId ], ['nilai' => $priorityVector[$alt->id] * $bobotKriteria[$kriteria->id]] ); } } } return true; } catch (\Exception $e) { \Log::error('Error in hitungBobotAlternatif: ' . $e->getMessage()); throw $e; } } // 4. Tampilkan form perbandingan alternatif public function tampilPerbandingan() { try { // Ambil data dari session $alternatifsByKomponen = session('alternatifs_by_komponen'); if (!$alternatifsByKomponen) { return redirect()->route('alternatif.view') ->with('error', 'Silakan lihat data alternatif terlebih dahulu'); } // Ambil semua kriteria $kriterias = \App\Models\Kriteria::all(); // Data sudah siap di session, tampilkan view dengan data kriteria return view('admin.alternatif.perbandingan', [ 'alternatifsByKomponen' => $alternatifsByKomponen, 'kriterias' => $kriterias ]); } catch (\Exception $e) { \Log::error('Error in tampilPerbandingan: ' . $e->getMessage()); return redirect()->route('alternatif.view') ->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); } } public function simpanNormalisasi(Request $request) { try { // Ambil data normalisasi dari request dalam format JSON $normalisasiDataJson = $request->input('normalisasi_data_json'); if (!$normalisasiDataJson) { return redirect()->back()->with('error', 'Data normalisasi tidak ditemukan'); } // Decode JSON data $normalisasiData = json_decode($normalisasiDataJson, true); if (!$normalisasiData || !isset($normalisasiData['data'])) { return redirect()->back()->with('error', 'Format data normalisasi tidak valid'); } // Simpan ke session session(['alternatifs_by_komponen' => $normalisasiData['data']]); // Log untuk debugging \Log::info('Data normalisasi disimpan:', [ 'normalisasi_data' => $normalisasiData['data'] ]); return redirect()->route('rekomendasi.hitung.otomatis') ->with('success', 'Data normalisasi berhasil disimpan'); } catch (\Exception $e) { \Log::error('Error in simpanNormalisasi:', [ 'message' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return redirect()->back() ->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); } } private function hitungNilaiPerbandingan($alt1, $alt2, $kriteria) { switch ($kriteria->kode) { case 'C1': // Lemak (cost) return $alt2['lemak'] > 0 ? $alt1['lemak_invers'] / $alt2['lemak_invers'] : 1; case 'C2': // Natrium (cost) return $alt2['natrium'] > 0 ? $alt1['natrium_invers'] / $alt2['natrium_invers'] : 1; case 'C3': // Energi (benefit) return $alt2['energi'] > 0 ? $alt1['energi'] / $alt2['energi'] : 1; case 'C4': // Karbohidrat (benefit) return $alt2['karbohidrat'] > 0 ? $alt1['karbohidrat'] / $alt2['karbohidrat'] : 1; default: return 1; } } private function getNilaiKriteria($makanan, $kriteria) { switch (strtolower($kriteria->nama)) { case 'energi': return $makanan->energi; case 'lemak': return $makanan->lemak; case 'karbohidrat': return $makanan->karbohidrat; case 'natrium': return $makanan->natrium; default: return 0; } } private function normalisasiNilai($nilai, $kriteria, $makanans) { // Hitung jumlah total untuk kriteria ini $total = 0; foreach ($makanans as $makanan) { $nilaiKriteria = $this->getNilaiKriteria($makanan, $kriteria); $total += $nilaiKriteria; } // Normalisasi sesuai perhitungan manual (nilai/jumlah) // Hindari pembagian dengan 0 return $total > 0 ? $nilai / $total : 0; } }