From e622cda9f31301838023ec94acfbe937fad33261 Mon Sep 17 00:00:00 2001 From: MufridaFaraDiani27 Date: Wed, 10 Jun 2026 02:43:04 +0700 Subject: [PATCH] perubahan --- app/Http/Controllers/AnalisisController.php | 8 +- .../Auth/ForgotPasswordController.php | 1 + app/Http/Controllers/DashboardController.php | 678 ++++++++------- app/Http/Controllers/LaporanController.php | 9 +- .../Controllers/RekomendasiController.php | 126 ++- app/Http/Controllers/UlasanController.php | 268 +++--- ..._182544_add_foreign_keys_to_all_tables.php | 69 ++ resources/views/analisis/index.blade.php | 7 +- resources/views/rekomendasi/index.blade.php | 344 +++++--- scraper/analisis.py | 770 ++++++++---------- scraper/analisis_backup.py | 485 ----------- scraper/analisis_baru.py | 329 -------- scraper/scraping_pipeline.py | 1 + .../10ee49aecb46b4eac9b0217a359f795e.php | 5 - .../4ddda1d400d20a99c44d449f2ad28fac.php | 117 +++ .../5fd932f7a53f435288301570d161158b.php | 377 +++++++++ .../7ac7144038b7ed595f7947eb2e474209.php | 605 -------------- .../7d9c5f85ffaf54d07a80af266d96aade.php | 442 ++++++++++ .../83200b6ca19a8da36ba75ad680d43e19.php | 339 +++++--- .../fb843eb1366f55ecbc0d64d0bcaf77ad.php | 7 +- .../fe5b23b6d58765df42d452d1a22bfa55.php | 35 - storage/models/model_periode_20.pkl | Bin 0 -> 3863 bytes storage/models/vectorizer_periode_20.pkl | Bin 0 -> 3415 bytes 23 files changed, 2437 insertions(+), 2585 deletions(-) create mode 100644 database/migrations/2026_06_09_182544_add_foreign_keys_to_all_tables.php delete mode 100644 scraper/analisis_backup.py delete mode 100644 scraper/analisis_baru.py delete mode 100644 storage/framework/views/10ee49aecb46b4eac9b0217a359f795e.php create mode 100644 storage/framework/views/4ddda1d400d20a99c44d449f2ad28fac.php create mode 100644 storage/framework/views/5fd932f7a53f435288301570d161158b.php delete mode 100644 storage/framework/views/7ac7144038b7ed595f7947eb2e474209.php create mode 100644 storage/framework/views/7d9c5f85ffaf54d07a80af266d96aade.php delete mode 100644 storage/framework/views/fe5b23b6d58765df42d452d1a22bfa55.php create mode 100644 storage/models/model_periode_20.pkl create mode 100644 storage/models/vectorizer_periode_20.pkl diff --git a/app/Http/Controllers/AnalisisController.php b/app/Http/Controllers/AnalisisController.php index 5b47081..66ac2c2 100644 --- a/app/Http/Controllers/AnalisisController.php +++ b/app/Http/Controllers/AnalisisController.php @@ -10,6 +10,7 @@ class AnalisisController extends Controller { public function index(Request $request) { + ; $periodeList = DB::table('periode_analisis as p') ->whereExists(function ($q) { $q->select(DB::raw(1)) @@ -55,7 +56,8 @@ public function index(Request $request) $query->where('wisata', $request->wisata); } - $hasil = $query->orderBy('id', 'desc')->paginate(10); + $hasil = $query->orderBy('created_at', 'desc')->paginate(10); + $standalone = true; return view('analisis.index', compact( @@ -64,7 +66,9 @@ public function index(Request $request) 'evaluasi', 'standalone', 'jumlahKelasAnalisis', - 'totalHasilAnalisis' + 'totalHasilAnalisis', + 'periodeAktif', // ← tambahkan + 'periodeList', // ← tambahkan )); } } \ No newline at end of file diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index cd08b9e..342ad0a 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Mail; use Illuminate\Support\Str; use Carbon\Carbon; +use Illuminate\Support\Facades\Password; class ForgotPasswordController extends Controller { diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 75939a4..9fb3dbf 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -60,330 +60,412 @@ class DashboardController extends Controller ], ]; - public function index(Request $request) -{ - $tab = $request->input('tab', 'dashboard'); - $wisata = $tab === 'analisis' ? $request->wisata : null; - $destinasi = $tab === 'rekomendasi' ? $request->destinasi : null; + public function index(Request $request) + { + $tab = $request->input('tab', 'dashboard'); + $wisata = $tab === 'analisis' ? $request->wisata : null; + $destinasi = $tab === 'rekomendasi' ? $request->destinasi : null; - // ================================================================ - // PERIODE BULAN YANG DIPILIH - // ================================================================ - $periodeBulan = $request->input('periode_bulan', now()->format('Y-m')); + $currentMonth = now()->month; + $currentYear = now()->year; - [$tahun, $bulan] = explode('-', $periodeBulan); + // ================================================================ + // AMBIL SEMUA PERIODE YANG PUNYA DATA + // ================================================================ + $periodeList = DB::table('periode_analisis as p') + ->whereExists(function ($q) { + $q->select(DB::raw(1))->from('ulasan as u')->whereColumn('u.periode_id', 'p.id'); + }) + ->orWhereExists(function ($q) { + $q->select(DB::raw(1))->from('hasil_analisis as h')->whereColumn('h.periode_id', 'p.id'); + }) + ->orderBy('p.tahun', 'desc') + ->orderBy('p.bulan', 'desc') + ->select('p.*') + ->get(); - // ================================================================ - // AMBIL SEMUA PERIODE & PERIODE AKTIF - // ================================================================ - $periodeList = DB::table('periode_analisis as p') - ->whereExists(function ($q) { - $q->select(DB::raw(1)) - ->from('ulasan as u') - ->whereColumn('u.periode_id', 'p.id'); - }) - ->orWhereExists(function ($q) { - $q->select(DB::raw(1)) - ->from('hasil_analisis as h') - ->whereColumn('h.periode_id', 'p.id'); - }) - ->orderBy('p.id', 'desc') - ->select('p.*') - ->get(); + // ================================================================ + // TENTUKAN PERIODE AKTIF + // Dashboard, Hasil Analisis, Rekomendasi: + // - Kalau request manual → pakai itu + // - Kalau tidak → cari bulan ini; kalau belum ada data → fallback ke bulan terakhir + // ================================================================ + if ($request->filled('periode_id')) { + // Prioritas utama: periode_id langsung (dikirim saat filter destinasi/wisata) + $periodeAktif = $periodeList->firstWhere('id', (int) $request->periode_id); + } elseif ($request->filled('periode_bulan')) { + [$tahun, $bulan] = explode('-', $request->periode_bulan); + $periodeAktif = $periodeList->first(fn($p) => + $p->tahun == (int) $tahun && $p->bulan == (int) $bulan + ); + } else { + // Cari bulan ini + $periodeAktif = $periodeList->first(fn($p) => + $p->bulan == $currentMonth && $p->tahun == $currentYear + ); + // Belum ada data bulan ini → fallback ke bulan terakhir yang ada data + if (!$periodeAktif) { + $periodeAktif = $periodeList->first(); // sudah urut desc + } + } - // Cocokkan pakai kolom bulan & tahun - $periodeAktif = $periodeList->first(function ($p) use ($tahun, $bulan) { - return $p->tahun == (int)$tahun && $p->bulan == (int)$bulan; - }); + $noDataPesan = null; + if (!$periodeAktif) { + $noDataPesan = 'Belum ada data.'; + } - // Jika tidak ada data di bulan tsb - $noDataPesan = null; - if (!$periodeAktif) { - $namaBulanDipilih = \Carbon\Carbon::createFromDate($tahun, $bulan, 1) - ->locale('id')->isoFormat('MMMM YYYY'); - $noDataPesan = "Tidak ada data untuk bulan {$namaBulanDipilih}."; - } + $periodeId = $periodeAktif->id ?? null; - $periodeId = $periodeAktif->id ?? null; + // ================================================================ + // EVALUASI MODEL + // ================================================================ + $evaluasi = DB::table('evaluasi_model') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->latest() + ->first(); - $evaluasi = DB::table('evaluasi_model') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->latest()->first(); - - // ================================================================ - // TOTAL ULASAN - // ================================================================ - $totalUlasan = DB::table('ulasan') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) - ->count(); - - // ================================================================ - // CEK ADA ANALISIS ATAU BELUM - // ================================================================ - $hasAnalisis = DB::table('hasil_analisis') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->count() > 0; - - // ================================================================ - // HITUNG SENTIMEN - // ================================================================ - $positif = DB::table('hasil_analisis') - ->where('sentimen', 'positif') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) - ->count(); - - $negatif = DB::table('hasil_analisis') - ->where('sentimen', 'negatif') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) - ->count(); - - $netral = DB::table('hasil_analisis') - ->where('sentimen', 'netral') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) - ->count(); - - $totalSentimen = $positif + $negatif + $netral; - - $stats = [ - 'total' => $totalUlasan, - 'positif_persen' => $totalSentimen > 0 ? round(($positif / $totalSentimen) * 100) : 0, - 'negatif_persen' => $totalSentimen > 0 ? round(($negatif / $totalSentimen) * 100) : 0, - 'netral_persen' => $totalSentimen > 0 ? round(($netral / $totalSentimen) * 100) : 0, - ]; - - // ================================================================ - // CHART DATA - // ================================================================ - // $chartSentimen = $hasAnalisis - // ? DB::table('hasil_analisis') - // ->select('sentimen', DB::raw('count(*) as total')) - // ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - // ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) - // ->groupBy('sentimen') - // ->orderByRaw("FIELD(sentimen, 'positif', 'negatif', 'netral')") - // ->get() - // : collect(); - - $rawSentimen = DB::table('hasil_analisis') - ->select('sentimen', DB::raw('count(*) as total')) - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) - ->groupBy('sentimen') - ->pluck('total', 'sentimen'); - - $chartSentimen = collect([ - ['sentimen' => 'positif', 'total' => $rawSentimen['positif'] ?? 0], - ['sentimen' => 'negatif', 'total' => $rawSentimen['negatif'] ?? 0], - ['sentimen' => 'netral', 'total' => $rawSentimen['netral'] ?? 0], - ]); - - $chartDestinasi = $hasAnalisis - ? DB::table('hasil_analisis') - ->select( - 'wisata', - DB::raw("SUM(CASE WHEN sentimen = 'positif' THEN 1 ELSE 0 END) as positif"), - DB::raw("SUM(CASE WHEN sentimen = 'negatif' THEN 1 ELSE 0 END) as negatif"), - DB::raw("SUM(CASE WHEN sentimen = 'netral' THEN 1 ELSE 0 END) as netral") - ) + // ================================================================ + // TOTAL ULASAN + // ================================================================ + $totalUlasan = DB::table('ulasan') ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) + ->count(); + + // ================================================================ + // CEK ADA ANALISIS ATAU BELUM + // ================================================================ + $hasAnalisis = DB::table('hasil_analisis') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->count() > 0; + + // ================================================================ + // HITUNG SENTIMEN + // ================================================================ + $positif = DB::table('hasil_analisis') + ->where('sentimen', 'positif') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) + ->count(); + + $negatif = DB::table('hasil_analisis') + ->where('sentimen', 'negatif') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) + ->count(); + + $netral = DB::table('hasil_analisis') + ->where('sentimen', 'netral') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) + ->count(); + + $totalSentimen = $positif + $negatif + $netral; + + $stats = [ + 'total' => $totalUlasan, + 'positif_persen' => $totalSentimen > 0 ? round(($positif / $totalSentimen) * 100) : 0, + 'negatif_persen' => $totalSentimen > 0 ? round(($negatif / $totalSentimen) * 100) : 0, + 'netral_persen' => $totalSentimen > 0 ? round(($netral / $totalSentimen) * 100) : 0, + ]; + + // ================================================================ + // CHART DATA + // ================================================================ + $rawSentimen = DB::table('hasil_analisis') + ->select('sentimen', DB::raw('count(*) as total')) + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) + ->groupBy('sentimen') + ->pluck('total', 'sentimen'); + + $chartSentimen = collect([ + ['sentimen' => 'positif', 'total' => $rawSentimen['positif'] ?? 0], + ['sentimen' => 'negatif', 'total' => $rawSentimen['negatif'] ?? 0], + ['sentimen' => 'netral', 'total' => $rawSentimen['netral'] ?? 0], + ]); + + $chartDestinasi = $hasAnalisis + ? DB::table('hasil_analisis') + ->select( + 'wisata', + DB::raw("SUM(CASE WHEN sentimen = 'positif' THEN 1 ELSE 0 END) as positif"), + DB::raw("SUM(CASE WHEN sentimen = 'negatif' THEN 1 ELSE 0 END) as negatif"), + DB::raw("SUM(CASE WHEN sentimen = 'netral' THEN 1 ELSE 0 END) as netral") + ) + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) + ->groupBy('wisata') + ->get() + : collect(); + + // ================================================================ + // TOTAL PER DESTINASI + WORD CLOUD + LAST UPDATE + // ================================================================ + $totalPerWisata = DB::table('ulasan') + ->select('wisata', DB::raw('count(*) as total')) + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) ->groupBy('wisata') - ->get() - : collect(); + ->get(); - // ================================================================ - // TOTAL PER DESTINASI + WORD CLOUD + LAST UPDATE - // ================================================================ - $totalPerWisata = DB::table('ulasan') - ->select('wisata', DB::raw('count(*) as total')) - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->groupBy('wisata') - ->get(); + $texts = DB::table('hasil_analisis') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->pluck('ulasan_bersih') + ->toArray(); + $allText = count($texts) > 0 ? implode(' ', $texts) : ''; - $texts = DB::table('hasil_analisis') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->pluck('ulasan_bersih') - ->toArray(); - $allText = count($texts) > 0 ? implode(' ', $texts) : ''; + $lastUpdate = DB::table('hasil_analisis') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->latest('created_at') + ->value('created_at'); - $lastUpdate = DB::table('hasil_analisis') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->latest('created_at')->value('created_at'); + $destinasiList = DB::table('hasil_analisis') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->select('wisata')->distinct()->pluck('wisata'); + $wisataList = $destinasiList; - $destinasiList = DB::table('hasil_analisis') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->select('wisata')->distinct()->pluck('wisata'); - $wisataList = $destinasiList; + $totalDestinasiAnalisis = DB::table('hasil_analisis') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->distinct('wisata')->count('wisata'); - $totalDestinasiAnalisis = DB::table('hasil_analisis') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->distinct('wisata') - ->count('wisata'); + $totalDestinasiBerkeluhan = DB::table('hasil_analisis') + ->where('sentimen', 'negatif') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->distinct('wisata')->count('wisata'); - $totalDestinasiBerkeluhan = DB::table('hasil_analisis') - ->where('sentimen', 'negatif') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->distinct('wisata') - ->count('wisata'); + $hasil = DB::table('hasil_analisis') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) + ->orderBy('created_at', 'desc') + ->paginate(10); - $hasil = DB::table('hasil_analisis') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) - ->orderByDesc('ulasan_id') - ->paginate(10); + $jumlahKelasAnalisis = DB::table('hasil_analisis') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) + ->distinct('sentimen')->count('sentimen'); - $jumlahKelasAnalisis = DB::table('hasil_analisis') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) - ->distinct('sentimen') - ->count('sentimen'); + $totalHasilAnalisis = DB::table('hasil_analisis') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) + ->count(); - $totalHasilAnalisis = DB::table('hasil_analisis') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->when($wisata && $wisata != 'Semua Destinasi', fn($q) => $q->where('wisata', $wisata)) - ->count(); + // ================================================================ + // REKOMENDASI + // ================================================================ + $ulasanNegatif = DB::table('hasil_analisis') + ->where('sentimen', 'negatif') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->when($destinasi && $destinasi != 'Semua Destinasi', fn($q) => $q->where('wisata', $destinasi)) + ->pluck('ulasan_bersih') + ->toArray(); - // ================================================================ - // REKOMENDASI - // ================================================================ - $ulasanNegatif = DB::table('hasil_analisis') - ->where('sentimen', 'negatif') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->when($destinasi && $destinasi != 'Semua Destinasi', fn($q) => $q->where('wisata', $destinasi)) - ->pluck('ulasan_bersih') - ->toArray(); + $textRek = strtolower(implode(' ', $ulasanNegatif)); + $wordsRek = array_filter(preg_split('/\s+/', $textRek)); - $textRek = strtolower(implode(' ', $ulasanNegatif)); - $wordsRek = array_filter(preg_split('/\s+/', $textRek)); - - $issueSkor = []; - foreach ($this->issueRules as $kategori => $rule) { - $skor = 0; - foreach ($rule['keywords'] as $kw) { - $skor += substr_count($textRek, $kw); + $issueSkor = []; + foreach ($this->issueRules as $kategori => $rule) { + $skor = 0; + foreach ($rule['keywords'] as $kw) { + $skor += substr_count($textRek, $kw); + } + $issueSkor[$kategori] = $skor; } - $issueSkor[$kategori] = $skor; - } - arsort($issueSkor); - $issueSkorAktif = array_filter($issueSkor, fn($skor) => $skor > 0); - $totalSkorIsu = array_sum($issueSkorAktif) ?: 1; + arsort($issueSkor); + $issueSkorAktif = array_filter($issueSkor, fn($skor) => $skor > 0); + $totalSkorIsu = array_sum($issueSkorAktif) ?: 1; - $isuUtama = []; - foreach (array_slice($issueSkorAktif, 0, 5, true) as $nama => $skor) { - $isuUtama[] = [ - 'nama' => $nama, - 'skor' => $skor, - 'persen' => round(($skor / $totalSkorIsu) * 100), - 'color' => $this->issueRules[$nama]['color'], - 'icon' => $this->issueRules[$nama]['icon'], + $isuUtama = []; + foreach (array_slice($issueSkorAktif, 0, 5, true) as $nama => $skor) { + $isuUtama[] = [ + 'nama' => $nama, + 'skor' => $skor, + 'persen' => round(($skor / $totalSkorIsu) * 100), + 'color' => $this->issueRules[$nama]['color'], + 'icon' => $this->issueRules[$nama]['icon'], + ]; + } + + $stopwords = [ + 'yang','dan','di','ke','dari','untuk','dengan','ini','itu','tidak', + 'ada','juga','sangat','lebih','sudah','bisa','tapi','karena','pada', + 'akan','atau','saya','kami','mereka','nya','ga','gak','udah','pas', + 'kita','sih','deh','aja','ya','yg','tp','bgt','jadi','biar','kalau', ]; + $freq = array_count_values($wordsRek); + foreach ($stopwords as $sw) { unset($freq[$sw]); } + foreach (array_keys($freq) as $w) { + if (mb_strlen($w) < 3) unset($freq[$w]); + } + arsort($freq); + $kataDominan = array_slice($freq, 0, 10, true); + + $saranPerbaikan = []; + foreach (array_slice($issueSkorAktif, 0, 3, true) as $nama => $skor) { + $saranPerbaikan[] = [ + 'nama' => 'Perbaikan ' . $nama, + 'tip' => $this->issueRules[$nama]['saran']['tip'], + 'icon' => $this->issueRules[$nama]['icon'], + ]; + } + + $prioritas = []; + $rank = 1; + foreach (array_slice($issueSkorAktif, 0, 3, true) as $nama => $skor) { + $prioritas[] = [ + 'rank' => $rank++, + 'nama' => $nama, + 'icon' => $this->issueRules[$nama]['icon'], + 'actions' => $this->issueRules[$nama]['saran']['actions'], + 'dampak' => $this->issueRules[$nama]['saran']['dampak'], + 'color' => $this->issueRules[$nama]['color'], + ]; + } + + $positifRek = DB::table('hasil_analisis') + ->where('sentimen', 'positif') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->when($destinasi && $destinasi != 'Semua Destinasi', fn($q) => $q->where('wisata', $destinasi)) + ->count(); + + $negatifRek = DB::table('hasil_analisis') + ->where('sentimen', 'negatif') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->when($destinasi && $destinasi != 'Semua Destinasi', fn($q) => $q->where('wisata', $destinasi)) + ->count(); + + $totalSentimenRek = DB::table('hasil_analisis') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->when($destinasi && $destinasi != 'Semua Destinasi', fn($q) => $q->where('wisata', $destinasi)) + ->count() ?: 1; + + $totalNegatif = $negatifRek; + $persenNegatif = round(($negatifRek / $totalSentimenRek) * 100, 2); + $tingkatKepuasan = round(($positifRek / $totalSentimenRek) * 100); + $labelKepuasan = match(true) { + $tingkatKepuasan >= 80 => 'Baik', + $tingkatKepuasan >= 60 => 'Sedang', + default => 'Kurang', + }; + $isuDominan = $isuUtama[0]['nama'] ?? '-'; + $isuDominanPersen = $isuUtama[0]['persen'] ?? 0; + + // --- Variabel tambahan untuk tab Rekomendasi --- + $netralRek = DB::table('hasil_analisis') + ->where('sentimen', 'netral') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->when($destinasi && $destinasi != 'Semua Destinasi', fn($q) => $q->where('wisata', $destinasi)) + ->count(); + + $totalUlasanRek = $positifRek + $negatifRek + $netralRek; + $totalPositif = $positifRek; + $totalNetral = $netralRek; + $persenPositif = $totalUlasanRek > 0 ? round(($positifRek / $totalUlasanRek) * 100) : 0; + $persenNetral = $totalUlasanRek > 0 ? round(($netralRek / $totalUlasanRek) * 100) : 0; + + // Isu negatif (sudah ada dari $isuUtama) + $isuNegatif = $isuUtama; + + // Isu netral (rule-based dari ulasan netral) + $ulasanNetralRek = DB::table('hasil_analisis') + ->where('sentimen', 'netral') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->when($destinasi && $destinasi != 'Semua Destinasi', fn($q) => $q->where('wisata', $destinasi)) + ->pluck('ulasan_bersih') + ->toArray(); + $textNetral = strtolower(implode(' ', $ulasanNetralRek)); + $skorNetral = []; + foreach ($this->issueRules as $kategori => $rule) { + $skor = 0; + foreach ($rule['keywords'] as $kw) { $skor += substr_count($textNetral, $kw); } + $skorNetral[$kategori] = $skor; + } + arsort($skorNetral); + $totalSkorNetral = array_sum($skorNetral) ?: 1; + $isuNetral = []; + foreach (array_slice($skorNetral, 0, 5, true) as $nama => $skor) { + $isuNetral[] = [ + 'nama' => $nama, + 'skor' => $skor, + 'persen' => round(($skor / $totalSkorNetral) * 100), + 'color' => $this->issueRules[$nama]['color'], + 'icon' => $this->issueRules[$nama]['icon'], + ]; + } + + // Isu positif (rule-based dari ulasan positif) + $ulasanPositifRek = DB::table('hasil_analisis') + ->where('sentimen', 'positif') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->when($destinasi && $destinasi != 'Semua Destinasi', fn($q) => $q->where('wisata', $destinasi)) + ->pluck('ulasan_bersih') + ->toArray(); + $textPositif = strtolower(implode(' ', $ulasanPositifRek)); + $skorPositif = []; + foreach ($this->issueRules as $kategori => $rule) { + $skor = 0; + foreach ($rule['keywords'] as $kw) { $skor += substr_count($textPositif, $kw); } + $skorPositif[$kategori] = $skor; + } + arsort($skorPositif); + $totalSkorPositif = array_sum($skorPositif) ?: 1; + $isuPositif = []; + foreach (array_slice($skorPositif, 0, 5, true) as $nama => $skor) { + $isuPositif[] = [ + 'nama' => $nama, + 'skor' => $skor, + 'persen' => round(($skor / $totalSkorPositif) * 100), + 'color' => $this->issueRules[$nama]['color'], + 'icon' => $this->issueRules[$nama]['icon'], + ]; + } + + // ================================================================ + // RETURN VIEW + // ================================================================ + return view('dashboard', compact( + 'stats', + 'chartSentimen', + 'chartDestinasi', + 'totalPerWisata', + 'allText', + 'lastUpdate', + 'hasAnalisis', + 'destinasiList', + 'wisataList', + 'totalDestinasiAnalisis', + 'totalDestinasiBerkeluhan', + 'hasil', + 'jumlahKelasAnalisis', + 'totalHasilAnalisis', + 'evaluasi', + 'totalNegatif', + 'persenNegatif', + 'persenPositif', + 'persenNetral', + 'tingkatKepuasan', + 'labelKepuasan', + 'isuUtama', + 'isuNegatif', + 'isuNetral', + 'isuPositif', + 'isuDominan', + 'isuDominanPersen', + 'kataDominan', + 'saranPerbaikan', + 'prioritas', + 'periodeId', + 'totalPositif', + 'totalNetral', + 'periodeList', + 'periodeAktif', + 'noDataPesan', + 'currentMonth', + 'currentYear', + ))->with([ + 'scraperDestinations' => $this->scraperDestinations(), + ]); } - $stopwords = [ - 'yang','dan','di','ke','dari','untuk','dengan','ini','itu','tidak', - 'ada','juga','sangat','lebih','sudah','bisa','tapi','karena','pada', - 'akan','atau','saya','kami','mereka','nya','ga','gak','udah','pas', - 'kita','sih','deh','aja','ya','yg','tp','bgt','jadi','biar','kalau', - ]; - $freq = array_count_values($wordsRek); - foreach ($stopwords as $sw) { unset($freq[$sw]); } - foreach (array_keys($freq) as $w) { - if (mb_strlen($w) < 3) unset($freq[$w]); - } - arsort($freq); - $kataDominan = array_slice($freq, 0, 10, true); - - $saranPerbaikan = []; - foreach (array_slice($issueSkorAktif, 0, 3, true) as $nama => $skor) { - $saranPerbaikan[] = [ - 'nama' => 'Perbaikan ' . $nama, - 'tip' => $this->issueRules[$nama]['saran']['tip'], - 'icon' => $this->issueRules[$nama]['icon'], - ]; - } - - $prioritas = []; - $rank = 1; - foreach (array_slice($issueSkorAktif, 0, 3, true) as $nama => $skor) { - $prioritas[] = [ - 'rank' => $rank++, - 'nama' => $nama, - 'icon' => $this->issueRules[$nama]['icon'], - 'actions' => $this->issueRules[$nama]['saran']['actions'], - 'dampak' => $this->issueRules[$nama]['saran']['dampak'], - 'color' => $this->issueRules[$nama]['color'], - ]; - } - - $positifRek = DB::table('hasil_analisis') - ->where('sentimen', 'positif') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->when($destinasi && $destinasi != 'Semua Destinasi', fn($q) => $q->where('wisata', $destinasi)) - ->count(); - - $negatifRek = DB::table('hasil_analisis') - ->where('sentimen', 'negatif') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->when($destinasi && $destinasi != 'Semua Destinasi', fn($q) => $q->where('wisata', $destinasi)) - ->count(); - - $totalSentimenRek = DB::table('hasil_analisis') - ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) - ->when($destinasi && $destinasi != 'Semua Destinasi', fn($q) => $q->where('wisata', $destinasi)) - ->count() ?: 1; - - $totalNegatif = $negatifRek; - $persenNegatif = round(($negatifRek / $totalSentimenRek) * 100, 2); - $tingkatKepuasan = round(($positifRek / $totalSentimenRek) * 100); - $labelKepuasan = match(true) { - $tingkatKepuasan >= 80 => 'Baik', - $tingkatKepuasan >= 60 => 'Sedang', - default => 'Kurang', - }; - $isuDominan = $isuUtama[0]['nama'] ?? '-'; - $isuDominanPersen = $isuUtama[0]['persen'] ?? 0; - - // ================================================================ - // RETURN VIEW - // ================================================================ - return view('dashboard', compact( - 'stats', - 'chartSentimen', - 'chartDestinasi', - 'totalPerWisata', - 'allText', - 'lastUpdate', - 'hasAnalisis', - 'destinasiList', - 'wisataList', - 'totalDestinasiAnalisis', - 'totalDestinasiBerkeluhan', - 'hasil', - 'jumlahKelasAnalisis', - 'totalHasilAnalisis', - 'evaluasi', - 'totalNegatif', - 'persenNegatif', - 'tingkatKepuasan', - 'labelKepuasan', - 'isuUtama', - 'isuDominan', - 'isuDominanPersen', - 'kataDominan', - 'saranPerbaikan', - 'prioritas', - 'periodeList', - 'periodeAktif', - 'noDataPesan', // ← tambahan baru - ))->with([ - 'scraperDestinations' => $this->scraperDestinations(), - ]); -} - private function scraperDestinations(): array { return [ diff --git a/app/Http/Controllers/LaporanController.php b/app/Http/Controllers/LaporanController.php index 7ba8316..76fe09c 100644 --- a/app/Http/Controllers/LaporanController.php +++ b/app/Http/Controllers/LaporanController.php @@ -27,6 +27,7 @@ public function downloadBulanan($periode) $data['evaluasi'] = DB::table('evaluasi_model') + ->where('periode_id', $periode) ->orderByDesc('id') ->first() ?? (object)['accuracy' => 0]; @@ -158,10 +159,10 @@ public function downloadTahunan(Request $request) // AKURASI $akurasi = DB::table('evaluasi_model') - ->latest('id') - ->value('accuracy') - ?? 0; - + ->whereIn('periode_id', $periodeIds) + ->avg('accuracy') ?? 0; + + // TOTAL WISATA $totalWisata = DB::table('hasil_analisis') diff --git a/app/Http/Controllers/RekomendasiController.php b/app/Http/Controllers/RekomendasiController.php index 0e44e9a..7b47a55 100644 --- a/app/Http/Controllers/RekomendasiController.php +++ b/app/Http/Controllers/RekomendasiController.php @@ -74,10 +74,37 @@ public function index(Request $request) ->distinct() ->pluck('wisata'); - // --- 2. Query dengan filter destinasi --- + // --- 2. Query dengan filter destinasi & periode --- $queryAll = HasilAnalisis::query(); $queryFilter = HasilAnalisis::query(); + // Resolve periode dari request (periode_bulan atau periode_id) + $periodeId = null; + if ($request->filled('periode_id')) { + $periodeId = (int) $request->periode_id; + } elseif ($request->filled('periode_bulan')) { + [$tahunParam, $bulanParam] = explode('-', $request->periode_bulan); + $periodeRow = DB::table('periode_analisis') + ->where('bulan', (int) $bulanParam) + ->where('tahun', (int) $tahunParam) + ->first(); + $periodeId = $periodeRow?->id; + } + + // Kalau tidak ada periode di request → pakai periode bulan ini + if (!$periodeId) { + $periodeRow = DB::table('periode_analisis') + ->where('bulan', now()->month) + ->where('tahun', now()->year) + ->first(); + $periodeId = $periodeRow?->id; + } + + if ($periodeId) { + $queryAll->where('periode_id', $periodeId); + $queryFilter->where('periode_id', $periodeId); + } + if ($request->filled('destinasi')) { $queryFilter->where('wisata', $request->destinasi); } @@ -89,7 +116,19 @@ public function index(Request $request) $totalNetral = (clone $queryFilter)->where('sentimen', 'netral')->count(); $persenNegatif = $totalUlasan > 0 ? round(($totalNegatif / $totalUlasan) * 100, 2) : 0; - $tingkatKepuasan = $totalUlasan > 0 ? round(($totalPositif / $totalUlasan) * 100) : 0; + $persenPositif = $totalUlasan > 0 ? round(($totalPositif / $totalUlasan) * 100) : 0; + $persenNetral = $totalUlasan > 0 ? round(($totalNetral / $totalUlasan) * 100) : 0; + $tingkatKepuasan = $persenPositif; + + // --- 3a. Statistik destinasi --- + $totalDestinasiAnalisis = $queryAll->distinct('wisata')->count('wisata'); + $totalDestinasiBerkeluhan = (clone $queryAll)->where('sentimen', 'negatif') + ->distinct('wisata')->count('wisata'); + + // --- 3b. Periode aktif --- + $periodeAktif = $periodeId + ? DB::table('periode_analisis')->find($periodeId) + : null; // Label kepuasan $labelKepuasan = match(true) { @@ -147,6 +186,80 @@ public function index(Request $request) ]; } + // --- 6a. Isu negatif (untuk kolom tabel negatif di tampilan) --- + $isuNegatif = []; + foreach (array_slice($issueSkor, 0, 5, true) as $nama => $skor) { + $isuNegatif[] = [ + 'nama' => $nama, + 'skor' => $skor, + 'persen' => round(($skor / $totalSkorIsu) * 100), + 'color' => $this->issueRules[$nama]['color'], + 'icon' => $this->issueRules[$nama]['icon'], + ]; + } + + // --- 6b. Isu netral (rule-based dari ulasan netral) --- + $ulasanNetral = (clone $queryFilter) + ->where('sentimen', 'netral') + ->get(['ulasan_bersih', 'ulasan']) + ->map(fn($r) => $r->ulasan_bersih ?? $r->ulasan ?? '') + ->filter() + ->toArray(); + + $textNetral = strtolower(implode(' ', $ulasanNetral)); + $skorNetral = []; + foreach ($this->issueRules as $kategori => $rule) { + $skor = 0; + foreach ($rule['keywords'] as $kw) { + $skor += substr_count($textNetral, $kw); + } + $skorNetral[$kategori] = $skor; + } + arsort($skorNetral); + $totalSkorNetral = array_sum($skorNetral) ?: 1; + + $isuNetral = []; + foreach (array_slice($skorNetral, 0, 5, true) as $nama => $skor) { + $isuNetral[] = [ + 'nama' => $nama, + 'skor' => $skor, + 'persen' => round(($skor / $totalSkorNetral) * 100), + 'color' => $this->issueRules[$nama]['color'], + 'icon' => $this->issueRules[$nama]['icon'], + ]; + } + + // --- 6c. Isu positif (rule-based dari ulasan positif) --- + $ulasanPositif = (clone $queryFilter) + ->where('sentimen', 'positif') + ->get(['ulasan_bersih', 'ulasan']) + ->map(fn($r) => $r->ulasan_bersih ?? $r->ulasan ?? '') + ->filter() + ->toArray(); + + $textPositif = strtolower(implode(' ', $ulasanPositif)); + $skorPositif = []; + foreach ($this->issueRules as $kategori => $rule) { + $skor = 0; + foreach ($rule['keywords'] as $kw) { + $skor += substr_count($textPositif, $kw); + } + $skorPositif[$kategori] = $skor; + } + arsort($skorPositif); + $totalSkorPositif = array_sum($skorPositif) ?: 1; + + $isuPositif = []; + foreach (array_slice($skorPositif, 0, 5, true) as $nama => $skor) { + $isuPositif[] = [ + 'nama' => $nama, + 'skor' => $skor, + 'persen' => round(($skor / $totalSkorPositif) * 100), + 'color' => $this->issueRules[$nama]['color'], + 'icon' => $this->issueRules[$nama]['icon'], + ]; + } + // --- 7. Isu dominan (ranking 1) --- // FIX: guard jika tidak ada ulasan negatif sama sekali if (empty($isuUtama) || $isuUtama[0]['skor'] === 0) { @@ -210,14 +323,23 @@ public function index(Request $request) return view('rekomendasi.index', compact( 'destinasiList', 'destinasiAktif', + 'periodeId', 'totalUlasan', 'totalNegatif', 'totalPositif', 'totalNetral', 'persenNegatif', + 'persenPositif', + 'persenNetral', 'tingkatKepuasan', 'labelKepuasan', + 'totalDestinasiAnalisis', + 'totalDestinasiBerkeluhan', + 'periodeAktif', 'isuUtama', + 'isuNegatif', + 'isuNetral', + 'isuPositif', 'isuDominan', 'isuDominanPersen', 'kataDominan', diff --git a/app/Http/Controllers/UlasanController.php b/app/Http/Controllers/UlasanController.php index ca95dea..c8d45ce 100644 --- a/app/Http/Controllers/UlasanController.php +++ b/app/Http/Controllers/UlasanController.php @@ -12,100 +12,85 @@ class UlasanController extends Controller { public function index(Request $request) -{ - $periodeList = DB::table('periode_analisis as p') - ->whereExists(function ($q) { - $q->select(DB::raw(1)) - ->from('ulasan as u') - ->whereColumn('u.periode_id', 'p.id'); - }) - ->orWhereExists(function ($q) { - $q->select(DB::raw(1)) - ->from('hasil_analisis as h') - ->whereColumn('h.periode_id', 'p.id'); - }) - ->orderBy('p.tahun', 'desc') - ->orderBy('p.bulan', 'desc') - ->select('p.*') - ->get(); + { + $currentMonth = now()->month; + $currentYear = now()->year; - $availablePeriodeIds = $periodeList->pluck('id')->map(fn($id) => (string) $id); + // Ambil semua periode yang punya data (ulasan atau hasil analisis) + $periodeList = DB::table('periode_analisis as p') + ->whereExists(function ($q) { + $q->select(DB::raw(1))->from('ulasan as u')->whereColumn('u.periode_id', 'p.id'); + }) + ->orWhereExists(function ($q) { + $q->select(DB::raw(1))->from('hasil_analisis as h')->whereColumn('h.periode_id', 'p.id'); + }) + ->orderBy('p.tahun', 'desc') + ->orderBy('p.bulan', 'desc') + ->select('p.*') + ->get(); - $requestedPeriodeId = $request->filled('periode_id') - ? (string) $request->periode_id - : null; + // Kalau request manual dari dropdown → pakai itu + if ($request->filled('periode_id')) { + $periodeId = (int) $request->periode_id; + $periodeAktif = $periodeList->firstWhere('id', $periodeId) + ?? DB::table('periode_analisis')->find($periodeId); + } else { + // Cari / buat periode bulan ini (meski belum ada data → tetap tampil kosong) + $periodeBulanIni = DB::table('periode_analisis') + ->where('bulan', $currentMonth) + ->where('tahun', $currentYear) + ->first(); - // cari periode bulan & tahun sekarang - $currentMonth = now()->month; - $currentYear = now()->year; + if (!$periodeBulanIni) { + $periodeId = DB::table('periode_analisis')->insertGetId([ + 'nama' => now()->locale('id')->isoFormat('MMMM YYYY'), + 'bulan' => $currentMonth, + 'tahun' => $currentYear, + 'created_at' => now(), + 'updated_at' => now(), + ]); + $periodeAktif = DB::table('periode_analisis')->find($periodeId); + } else { + $periodeId = $periodeBulanIni->id; + $periodeAktif = $periodeBulanIni; + } + } - $currentPeriode = $periodeList->first(function ($periode) use ($currentMonth, $currentYear) { - return $periode->bulan == $currentMonth - && $periode->tahun == $currentYear; - }); + // Query ulasan berdasarkan periode aktif + $query = Ulasan::query()->where('periode_id', $periodeId); - // kalau bulan sekarang tidak ada → ambil periode terakhir - $defaultPeriodeId = $currentPeriode->id - ?? $periodeList->sortByDesc('id')->first()->id - ?? null; + if ($request->filled('search')) { + $search = $request->search; + $query->where(function ($q) use ($search) { + $q->where('ulasan', 'LIKE', "%$search%") + ->orWhere('wisata', 'LIKE', "%$search%"); + }); + } - // prioritas: - // 1. request manual dari dropdown - // 2. periode bulan sekarang - // 3. periode terakhir - $periodeId = $request->filled('periode_id') - && $availablePeriodeIds->contains($requestedPeriodeId) - ? $requestedPeriodeId - : $defaultPeriodeId; + if ($request->filled('wisata')) { + $query->where('wisata', 'LIKE', '%' . $request->wisata . '%'); + } - $periodeAktif = $periodeId - ? $periodeList->firstWhere('id', (int) $periodeId) - : null; + $filteredQuery = clone $query; + $totalUlasan = (clone $filteredQuery)->count(); + $totalWisata = (clone $filteredQuery)->distinct('wisata')->count('wisata'); + $lastUpdate = (clone $filteredQuery)->latest()->value('created_at'); - $query = Ulasan::query(); + $mentah = $query->orderBy('tanggal', 'desc')->paginate(20)->withQueryString(); - // 🔍 SEARCH (ulasan + wisata) - if ($request->filled('search')) { - $search = $request->search; - - $query->where(function ($q) use ($search) { - $q->where('ulasan', 'LIKE', "%$search%") - ->orWhere('wisata', 'LIKE', "%$search%"); - }); + return view('ulasan.index', compact( + 'mentah', + 'totalUlasan', + 'totalWisata', + 'lastUpdate', + 'periodeList', + 'periodeId', + 'periodeAktif', + 'currentMonth', + 'currentYear' + ))->with(['scraperDestinations' => $this->scraperDestinations()]); } - // 🔍 FILTER WISATA - if ($request->filled('wisata')) { - $query->where('wisata', 'LIKE', '%' . $request->wisata . '%'); - } - - // ← default/reset menampilkan periode terbaru - if ($periodeId) { - $query->where('periode_id', $periodeId); - } - - $filteredQuery = clone $query; - - $totalUlasan = (clone $filteredQuery)->count(); - $totalWisata = (clone $filteredQuery)->distinct('wisata')->count('wisata'); - $lastUpdate = (clone $filteredQuery)->latest()->value('created_at'); - - $mentah = $query->orderBy('tanggal', 'desc')->paginate(20)->withQueryString(); - - return view('ulasan.index', compact( - 'mentah', - 'totalUlasan', - 'totalWisata', - 'lastUpdate', - 'periodeList', - 'periodeId', - 'periodeAktif', - 'defaultPeriodeId' - ))->with([ - 'scraperDestinations' => $this->scraperDestinations(), - ]); -} - public function riwayat() { $riwayat = DB::table('periode_analisis as p') @@ -249,62 +234,65 @@ public function importRiwayat(Request $request) } public function ambilData(Request $request) - { - $this->allowLongRunningRequest(); +{ + $this->allowLongRunningRequest(); - $validated = $request->validate([ - 'periode_bulan' => ['nullable', 'date_format:Y-m'], - 'wisata' => ['nullable', 'string'], - 'redirect_to' => ['nullable', 'in:dashboard,ulasan'], - ]); + $validated = $request->validate([ + 'periode_bulan' => ['nullable', 'date_format:Y-m'], + 'wisata' => ['nullable', 'string'], + 'redirect_to' => ['nullable', 'in:dashboard,ulasan'], + ]); - $periodeBulan = \Carbon\Carbon::createFromFormat( - 'Y-m', - $validated['periode_bulan'] ?? now()->format('Y-m') - ); - $startDate = $periodeBulan->copy()->startOfMonth()->toDateString(); - $endDate = $periodeBulan->copy()->endOfMonth()->toDateString(); + $periodeBulan = \Carbon\Carbon::createFromFormat( + 'Y-m', + $validated['periode_bulan'] ?? now()->format('Y-m') + ); + $startDate = $periodeBulan->copy()->startOfMonth()->toDateString(); + $endDate = $periodeBulan->copy()->endOfMonth()->toDateString(); - $scrapingArguments = [ - '--start-date', $startDate, - '--end-date', $endDate, - ]; + $scrapingArguments = [ + '--start-date', $startDate, + '--end-date', $endDate, + ]; - if (!empty($validated['wisata'])) { - $scrapingArguments[] = '--wisata'; - $scrapingArguments[] = $validated['wisata']; - } - - $scraping = $this->runPythonScript(base_path('scraper/scraping_pipeline.py'), 1800, $scrapingArguments); - - if (!$scraping['success']) { - return redirect()->route('ulasan.index') - ->with('error', 'Scraping gagal: ' . $scraping['output']); - } - - - $lokasiLabel = empty($validated['wisata']) ? 'semua lokasi' : $validated['wisata']; - $redirectParameters = [ - 'periode_id' => $periode->id ?? null, - ]; - - if (!empty($validated['wisata'])) { - $redirectParameters['wisata'] = $validated['wisata']; - } - - if (($validated['redirect_to'] ?? 'dashboard') === 'ulasan') { - return redirect()->route('ulasan.index', array_filter($redirectParameters)) - ->with('success', 'Data berhasil diambil' . $lokasiLabel . ' periode ' . $periodeBulan->translatedFormat('F Y') . '.'); - } - - if (!empty($validated['wisata'])) { - $redirectParameters['tab'] = 'analisis'; - } - - return redirect()->route('ulasan.index', array_filter($redirectParameters)) - ->with('success', 'Scraping berhasil dilakukan untuk ' . $lokasiLabel . ' periode ' . $periodeBulan->translatedFormat('F Y') . '. Silakan jalankan proses analisis.'); + if (!empty($validated['wisata'])) { + $scrapingArguments[] = '--wisata'; + $scrapingArguments[] = $validated['wisata']; } - + + $scraping = $this->runPythonScript(base_path('scraper/scraping_pipeline.py'), 1800, $scrapingArguments); + + if (!$scraping['success']) { + return redirect()->route('ulasan.index') + ->with('error', 'Scraping gagal: ' . $scraping['output']); + } + + // ✅ FIX: Ambil periode_id dari DB setelah scraping selesai + $periode = DB::table('periode_analisis') + ->where('bulan', $periodeBulan->month) + ->where('tahun', $periodeBulan->year) + ->first(); + + $lokasiLabel = empty($validated['wisata']) ? 'semua lokasi' : $validated['wisata']; + $redirectParameters = [ + 'periode_id' => $periode->id ?? null, + ]; + + if (!empty($validated['wisata'])) { + $redirectParameters['wisata'] = $validated['wisata']; + } + + if (($validated['redirect_to'] ?? 'ulasan') === 'ulasan') { + return redirect()->route('ulasan.index', array_filter($redirectParameters)) + ->with('success', 'Data berhasil diambil untuk ' . $lokasiLabel . ' periode ' . $periodeBulan->translatedFormat('F Y') . '.'); + } + + $redirectParameters['tab'] = 'analisis'; + + return redirect()->route('dashboard', array_filter($redirectParameters)) + ->with('success', 'Scraping berhasil untuk ' . $lokasiLabel . ' periode ' . $periodeBulan->translatedFormat('F Y') . '. Silakan jalankan proses analisis.'); + } + public function kosongkanPeriode(Request $request) { $periodeId = $request->validate([ @@ -399,6 +387,18 @@ private function runPythonScript(string $scriptPath, int $timeout, array $argume 'TMP' => getenv('TMP') ?: 'C:\\Users\\Mufrida Farah\\AppData\\Local\\Temp', ]); + // $envVars = array_merge($_SERVER, $_ENV, array_filter([ + // 'PATH' => getenv('PATH') ?: '/usr/bin:/usr/local/bin:/bin', + // 'HOME' => getenv('HOME') ?: (getenv('USERPROFILE') ?: null), + // 'TEMP' => getenv('TEMP') ?: (getenv('TMP') ?: sys_get_temp_dir()), + // 'TMP' => getenv('TMP') ?: (getenv('TEMP') ?: sys_get_temp_dir()), + // // Windows only — diisi kalau ada, di Linux otomatis null (dibuang array_filter) + // 'SystemRoot' => getenv('SystemRoot') ?: null, + // 'windir' => getenv('windir') ?: null, + // 'USERPROFILE' => getenv('USERPROFILE') ?: null, + // 'LOCALAPPDATA' => getenv('LOCALAPPDATA') ?: null, + // ])); + // 3. Masukkan $envVars ke parameter ketiga Process $process = new Process(array_merge([$pythonPath, $scriptPath], $arguments), base_path(), $envVars); $process->setTimeout($timeout); diff --git a/database/migrations/2026_06_09_182544_add_foreign_keys_to_all_tables.php b/database/migrations/2026_06_09_182544_add_foreign_keys_to_all_tables.php new file mode 100644 index 0000000..66d7f3e --- /dev/null +++ b/database/migrations/2026_06_09_182544_add_foreign_keys_to_all_tables.php @@ -0,0 +1,69 @@ +unsignedBigInteger('id')->change(); + $table->unsignedBigInteger('periode_id')->nullable()->change(); + }); + + Schema::table('hasil_analisis', function (Blueprint $table) { + $table->unsignedBigInteger('periode_id')->nullable()->change(); + $table->unsignedBigInteger('ulasan_id')->nullable()->change(); + }); + + Schema::table('evaluasi_model', function (Blueprint $table) { + $table->unsignedBigInteger('periode_id')->nullable()->change(); + }); + + // ── STEP 2: Tambah foreign key ── + Schema::table('ulasan', function (Blueprint $table) { + $table->foreign('periode_id') + ->references('id') + ->on('periode_analisis') + ->onDelete('cascade'); + }); + + Schema::table('hasil_analisis', function (Blueprint $table) { + $table->foreign('periode_id') + ->references('id') + ->on('periode_analisis') + ->onDelete('cascade'); + + $table->foreign('ulasan_id') + ->references('id') + ->on('ulasan') + ->onDelete('set null'); + }); + + Schema::table('evaluasi_model', function (Blueprint $table) { + $table->foreign('periode_id') + ->references('id') + ->on('periode_analisis') + ->onDelete('cascade'); + }); + } + + public function down(): void + { + Schema::table('ulasan', function (Blueprint $table) { + $table->dropForeign(['periode_id']); + }); + + Schema::table('hasil_analisis', function (Blueprint $table) { + $table->dropForeign(['periode_id']); + $table->dropForeign(['ulasan_id']); + }); + + Schema::table('evaluasi_model', function (Blueprint $table) { + $table->dropForeign(['periode_id']); + }); + } +}; \ No newline at end of file diff --git a/resources/views/analisis/index.blade.php b/resources/views/analisis/index.blade.php index 79dc46b..b9961fd 100644 --- a/resources/views/analisis/index.blade.php +++ b/resources/views/analisis/index.blade.php @@ -96,12 +96,7 @@ class="bg-blue-600 text-white px-4 rounded h-[46px]"> - - - - Lihat Riwayat - + diff --git a/resources/views/rekomendasi/index.blade.php b/resources/views/rekomendasi/index.blade.php index f432352..fb79cb2 100644 --- a/resources/views/rekomendasi/index.blade.php +++ b/resources/views/rekomendasi/index.blade.php @@ -3,6 +3,8 @@ $totalDestinasiAnalisis ??= 0; $totalDestinasiBerkeluhan ??= 0; $totalNegatif ??= 0; + $totalPositif ??= 0; + $totalNetral ??= 0; $totalUlasan ??= 0; $persenNegatif ??= 0; $tingkatKepuasan ??= 0; @@ -15,6 +17,37 @@ $prioritas ??= []; @endphp +@php + // $persenPositif, $persenNetral, $isuNegatif, $isuNetral, $isuPositif sudah dikirim dari controller + // Icon map untuk prioritas cards + $iconMap = [ + 'harga' => '🎫', + 'tiket' => '🎫', + 'kebersihan'=> '🧹', + 'parkir' => '🚗', + 'fasilitas' => '🏗️', + 'keramaian' => '👥', + 'akses' => '🛣️', + 'jalan' => '🛣️', + 'default' => '⚙️', + ]; +@endphp + + +
{{-- HEADER --}} @@ -22,7 +55,7 @@

Rekomendasi Layanan

- Rekomendasi dibuat dari ulasan negatif agar saran perbaikan fokus pada keluhan pengunjung. + Rekomendasi dibuat dari hasil analisis sentimen untuk perbaikan layanan destinasi wisata.

{{ $totalDestinasiAnalisis }} destinasi dianalisis, {{ $totalDestinasiBerkeluhan }} destinasi memiliki ulasan negatif. @@ -32,7 +65,10 @@ {{-- FILTER DESTINASI — submit ke /dashboard agar tab tidak reset --}}

- + + @if(request('periode_bulan')) + + @endif +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + +stopSection(); ?> +make('layouts.sentara', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?> \ No newline at end of file diff --git a/storage/framework/views/83200b6ca19a8da36ba75ad680d43e19.php b/storage/framework/views/83200b6ca19a8da36ba75ad680d43e19.php index af3d3b1..278f910 100644 --- a/storage/framework/views/83200b6ca19a8da36ba75ad680d43e19.php +++ b/storage/framework/views/83200b6ca19a8da36ba75ad680d43e19.php @@ -3,6 +3,8 @@ $totalDestinasiAnalisis ??= 0; $totalDestinasiBerkeluhan ??= 0; $totalNegatif ??= 0; + $totalPositif ??= 0; + $totalNetral ??= 0; $totalUlasan ??= 0; $persenNegatif ??= 0; $tingkatKepuasan ??= 0; @@ -15,6 +17,37 @@ $prioritas ??= []; ?> + '🎫', + 'tiket' => '🎫', + 'kebersihan'=> '🧹', + 'parkir' => '🚗', + 'fasilitas' => '🏗️', + 'keramaian' => '👥', + 'akses' => '🛣️', + 'jalan' => '🛣️', + 'default' => '⚙️', + ]; +?> + + +
@@ -22,7 +55,7 @@

Rekomendasi Layanan

- Rekomendasi dibuat dari ulasan negatif agar saran perbaikan fokus pada keluhan pengunjung. + Rekomendasi dibuat dari hasil analisis sentimen untuk perbaikan layanan destinasi wisata.

destinasi dianalisis, destinasi memiliki ulasan negatif. @@ -32,7 +65,10 @@

- + + + +