From 8e78dce219261e5ca5f53f571a67dda69f91f841 Mon Sep 17 00:00:00 2001 From: MufridaFaraDiani27 Date: Sun, 10 May 2026 01:44:07 +0700 Subject: [PATCH] nambah aja --- app/Http/Controllers/AnalisisController.php | 17 +- app/Http/Controllers/DashboardController.php | 398 +++++++++++++----- .../Controllers/RekomendasiController.php | 225 ++++++++-- app/Http/Controllers/UlasanController.php | 182 ++++---- app/Models/EvaluasiModel.php | 21 + app/Models/HasilAnalisis.php | 9 +- app/Providers/AppServiceProvider.php | 1 + config/app.php | 2 +- resources/views/analisis/index.blade.php | 190 ++++----- resources/views/dashboard.blade.php | 367 ++++++++-------- resources/views/rekomendasi/index.blade.php | 258 +++++------- resources/views/ulasan/index.blade.php | 263 +++++------- routes/web.php | 67 +-- scraper/analisis.py | 148 ++++--- 14 files changed, 1225 insertions(+), 923 deletions(-) create mode 100644 app/Models/EvaluasiModel.php diff --git a/app/Http/Controllers/AnalisisController.php b/app/Http/Controllers/AnalisisController.php index fd1b0b1..ebfbaa9 100644 --- a/app/Http/Controllers/AnalisisController.php +++ b/app/Http/Controllers/AnalisisController.php @@ -4,30 +4,29 @@ use Illuminate\Http\Request; use App\Models\HasilAnalisis; +use Illuminate\Support\Facades\DB; class AnalisisController extends Controller { public function index(Request $request) { - // Dropdown list destinasi unik + // Ambil data evaluasi model + $evaluasi = DB::table('evaluasi_model')->latest()->first(); + + // Dropdown wisata $wisataList = HasilAnalisis::select('wisata') ->distinct() ->pluck('wisata'); - // Query utama tabel + // Query hasil analisis dengan filter $query = HasilAnalisis::query(); - // Filter kalau user pilih destinasi tertentu if ($request->filled('wisata')) { $query->where('wisata', $request->wisata); } - // Data hasil analisis (pakai paginate biar rapi) - $hasil = $query->latest()->paginate(5); + $hasil = $query->latest()->paginate(10); - return view('analisis.index', compact( - 'hasil', - 'wisataList' - )); + return view('analisis.index', compact('hasil', 'wisataList', 'evaluasi')); } } \ No newline at end of file diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 5cc719d..8370d9c 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -7,138 +7,302 @@ class DashboardController extends Controller { - public function index(Request $request) - { - $wisata = $request->wisata; + private array $issueRules = [ + 'Kebersihan' => [ + 'keywords' => ['kotor', 'sampah', 'jorok', 'kumuh', 'toilet', 'wc', 'kamar mandi', 'limbah', 'bau', 'bersih'], + 'color' => 'red', + 'icon' => '๐Ÿงน', + 'saran' => [ + 'actions' => ['Tambah tempat sampah', 'Jadwal pembersihan rutin', 'Petugas kebersihan tambahan'], + 'dampak' => 'Kepuasan meningkat signifikan', + 'tip' => 'Tambah tempat sampah & jadwal pembersihan rutin.', + ], + ], + 'Parkir' => [ + 'keywords' => ['parkir', 'lahan parkir', 'tempat parkir', 'parkiran', 'motor', 'mobil'], + 'color' => 'orange', + 'icon' => '๐Ÿš—', + 'saran' => [ + 'actions' => ['Penataan area parkir', 'Tarif transparan', 'Petugas lebih ramah'], + 'dampak' => 'Mengurangi keluhan', + 'tip' => 'Sistem parkir lebih rapi dan transparan.', + ], + ], + 'Harga / Tiket' => [ + 'keywords' => ['mahal', 'harga', 'tiket', 'bayar', 'biaya', 'tarif', 'terjangkau'], + 'color' => 'yellow', + 'icon' => '๐ŸŽŸ๏ธ', + 'saran' => [ + 'actions' => ['Evaluasi harga tiket', 'Promo wisata', 'Diskon tertentu'], + 'dampak' => 'Daya tarik meningkat', + 'tip' => 'Penyesuaian harga tiket agar lebih terjangkau.', + ], + ], + 'Fasilitas' => [ + 'keywords' => ['fasilitas', 'gazebo', 'warung', 'kantin', 'kamar ganti', 'musholla', 'wifi', 'bangku', 'kursi'], + 'color' => 'green', + 'icon' => '๐Ÿ—๏ธ', + 'saran' => [ + 'actions' => ['Perbaiki fasilitas rusak', 'Tambah fasilitas umum', 'Perawatan berkala'], + 'dampak' => 'Kenyamanan pengunjung meningkat', + 'tip' => 'Fasilitas lengkap & terawat meningkatkan kepuasan.', + ], + ], + 'Keramaian' => [ + 'keywords' => ['ramai', 'macet', 'antri', 'sesak', 'penuh', 'padat', 'berdesakan'], + 'color' => 'blue', + 'icon' => '๐Ÿ‘ฅ', + 'saran' => [ + 'actions' => ['Atur kapasitas pengunjung', 'Sistem antrean', 'Jam kunjungan fleksibel'], + 'dampak' => 'Pengalaman pengunjung lebih nyaman', + 'tip' => 'Manajemen kapasitas pengunjung lebih baik.', + ], + ], + ]; + + public function index(Request $request) +{ + $wisata = $request->wisata; + $destinasi = $request->destinasi; + + // ================================================================ + // AMBIL SEMUA PERIODE & PERIODE AKTIF + // ================================================================ + $periodeList = DB::table('periode_analisis')->orderBy('id', 'desc')->get(); + $periodeAktif = $request->periode_id + ? DB::table('periode_analisis')->find($request->periode_id) + : DB::table('periode_analisis')->orderBy('id', 'desc')->first(); + $periodeId = $periodeAktif->id ?? null; + $evaluasi = DB::table('evaluasi_model') - ->latest() - ->first(); - // ============================= - // TOTAL ULASAN (DARI TABEL ULASAN) - // ============================= - $totalUlasan = DB::table('ulasan') - ->when($wisata && $wisata != 'Semua Destinasi', function ($q) use ($wisata) { - $q->where('wisata', $wisata); - }) - ->count(); + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->latest()->first(); - // ============================= - // CEK ADA ANALISIS ATAU BELUM - // ============================= - $hasAnalisis = DB::table('hasil_analisis')->count() > 0; + // ================================================================ + // 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(); - // ============================= - // HITUNG SENTIMEN (DARI HASIL ANALISIS) - // ============================= - $positif = DB::table('hasil_analisis') - ->where('sentimen', 'Positif') - ->when($wisata && $wisata != 'Semua Destinasi', function ($q) use ($wisata) { - $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; - $negatif = DB::table('hasil_analisis') - ->where('sentimen', 'Negatif') - ->when($wisata && $wisata != 'Semua Destinasi', function ($q) use ($wisata) { - $q->where('wisata', $wisata); - }) - ->count(); + // ================================================================ + // 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(); - $netral = DB::table('hasil_analisis') - ->where('sentimen', 'Netral') - ->when($wisata && $wisata != 'Semua Destinasi', function ($q) use ($wisata) { - $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(); - // ============================= - // PERSENTASE SENTIMEN - // ============================= - $totalSentimen = $positif + $negatif + $netral; + $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(); - $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, - ]; + $totalSentimen = $positif + $negatif + $netral; - // ============================= - // GRAFIK PIE (Distribusi Sentimen) - // ============================= - $chartSentimen = $hasAnalisis - ? DB::table('hasil_analisis') - ->select('sentimen', DB::raw('count(*) as total')) - ->when($wisata && $wisata != 'Semua Destinasi', function ($q) use ($wisata) { - $q->where('wisata', $wisata); - }) - ->groupBy('sentimen') - ->orderByRaw("FIELD(sentimen, 'Positif', 'Negatif', 'Netral')") // ๐Ÿ”ฅ INI KUNCINYA - ->get() - : collect(); + $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, + ]; - // ============================= - // GRAFIK BAR (Sentimen per Destinasi) - // ============================= - $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($wisata && $wisata != 'Semua Destinasi', function ($q) use ($wisata) { - $q->where('wisata', $wisata); - }) - ->groupBy('wisata') - - ->get() - : collect(); + // ================================================================ + // 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(); - // ============================= - // TOTAL DATA PER DESTINASI (DARI ULASAN) - // ============================= - $totalPerWisata = DB::table('ulasan') - ->select('wisata', DB::raw('count(*) as total')) + $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(); + ->get() + : collect(); - // ============================= - // WORD CLOUD (DARI PREPROCESSING) - // ============================= - $texts = DB::table('preprocessing_data')->pluck('stemming')->toArray(); - $allText = count($texts) > 0 ? implode(' ', $texts) : ''; + // ================================================================ + // 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(); - // ============================= - // LAST UPDATE - // ============================= - $lastUpdate = DB::table('hasil_analisis') - ->latest('created_at') - ->value('created_at'); + $texts = DB::table('hasil_analisis') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->pluck('ulasan_bersih') + ->toArray(); + $allText = count($texts) > 0 ? implode(' ', $texts) : ''; - $destinasiList = DB::table('ulasan') - ->select('wisata') - ->distinct() - ->pluck('wisata'); + $lastUpdate = DB::table('hasil_analisis') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->latest('created_at')->value('created_at'); -$hasil = DB::table('hasil_analisis') - ->when($wisata && $wisata != 'Semua Destinasi', function ($q) use ($wisata) { - $q->where('wisata', $wisata); - }) - ->paginate(10); + $destinasiList = DB::table('ulasan') + ->when($periodeId, fn($q) => $q->where('periode_id', $periodeId)) + ->select('wisata')->distinct()->pluck('wisata'); - return view('dashboard', compact( - 'stats', - 'chartSentimen', - 'chartDestinasi', - 'totalPerWisata', - 'allText', - 'lastUpdate', - 'hasAnalisis', - 'destinasiList', // WAJIB - 'hasil', - 'evaluasi' // WAJIB -)); + $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)) + ->paginate(10); + + // ================================================================ + // 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)); + + $issueSkor = []; + foreach ($this->issueRules as $kategori => $rule) { + $skor = 0; + foreach ($rule['keywords'] as $kw) { + $skor += substr_count($textRek, $kw); + } + $issueSkor[$kategori] = $skor; } + arsort($issueSkor); + $totalSkorIsu = array_sum($issueSkor) ?: 1; + + $isuUtama = []; + foreach (array_slice($issueSkor, 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($issueSkor, 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($issueSkor, 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', + 'hasil', + 'evaluasi', + 'totalNegatif', + 'persenNegatif', + 'tingkatKepuasan', + 'labelKepuasan', + 'isuUtama', + 'isuDominan', + 'isuDominanPersen', + 'kataDominan', + 'saranPerbaikan', + 'prioritas', + 'periodeList', // โ† tambahan + 'periodeAktif', // โ† tambahan + )); +} } \ No newline at end of file diff --git a/app/Http/Controllers/RekomendasiController.php b/app/Http/Controllers/RekomendasiController.php index 07c5893..9a3eb05 100644 --- a/app/Http/Controllers/RekomendasiController.php +++ b/app/Http/Controllers/RekomendasiController.php @@ -8,65 +8,208 @@ class RekomendasiController extends Controller { + /** + * Rule-based: mapping kategori isu ke keyword terkait + */ + private array $issueRules = [ + 'Kebersihan' => [ + 'keywords' => ['kotor', 'sampah', 'bersih', 'jorok', 'kumuh', 'toilet', 'wc', 'kamar mandi', 'limbah', 'bau'], + 'color' => 'red', + 'icon' => '๐Ÿงน', + 'saran' => [ + 'actions' => ['Tambah tempat sampah', 'Jadwal pembersihan rutin', 'Petugas kebersihan tambahan'], + 'dampak' => 'Kepuasan meningkat signifikan', + 'tip' => 'Tambah tempat sampah & jadwal pembersihan rutin.', + ], + ], + 'Parkir' => [ + 'keywords' => ['parkir', 'lahan parkir', 'tempat parkir', 'parkiran', 'motor', 'mobil'], + 'color' => 'orange', + 'icon' => '๐Ÿš—', + 'saran' => [ + 'actions' => ['Penataan area parkir', 'Tarif transparan', 'Petugas lebih ramah'], + 'dampak' => 'Mengurangi keluhan', + 'tip' => 'Sistem parkir lebih rapi dan transparan.', + ], + ], + 'Harga / Tiket' => [ + 'keywords' => ['mahal', 'harga', 'tiket', 'bayar', 'biaya', 'tarif', 'murah', 'terjangkau'], + 'color' => 'yellow', + 'icon' => '๐ŸŽŸ๏ธ', + 'saran' => [ + 'actions' => ['Evaluasi harga tiket', 'Promo wisata', 'Diskon tertentu'], + 'dampak' => 'Daya tarik meningkat', + 'tip' => 'Penyesuaian harga tiket agar lebih terjangkau.', + ], + ], + 'Fasilitas' => [ + 'keywords' => ['fasilitas', 'gazebo', 'warung', 'kantin', 'kamar ganti', 'musholla', 'wifi', 'bangku', 'kursi'], + 'color' => 'green', + 'icon' => '๐Ÿ—๏ธ', + 'saran' => [ + 'actions' => ['Perbaiki fasilitas rusak', 'Tambah fasilitas umum', 'Perawatan berkala'], + 'dampak' => 'Kenyamanan pengunjung meningkat', + 'tip' => 'Fasilitas lengkap & terawat meningkatkan kepuasan.', + ], + ], + 'Keramaian' => [ + 'keywords' => ['ramai', 'macet', 'antri', 'sesak', 'penuh', 'padat', 'berdesakan'], + 'color' => 'blue', + 'icon' => '๐Ÿ‘ฅ', + 'saran' => [ + 'actions' => ['Atur kapasitas pengunjung', 'Sistem antrean', 'Jam kunjungan fleksibel'], + 'dampak' => 'Pengalaman pengunjung lebih nyaman', + 'tip' => 'Manajemen kapasitas pengunjung lebih baik.', + ], + ], + ]; + public function index(Request $request) { - // dropdown destinasi unik - $destinasiList = HasilAnalisis::select('wisata') + // --- 1. Dropdown destinasi --- + $destinasiList = DB::table('hasil_analisis') + ->select('wisata') ->distinct() ->pluck('wisata'); - // query utama - $query = HasilAnalisis::query(); + // --- 2. Query dengan filter destinasi --- + $queryAll = HasilAnalisis::query(); + $queryFilter = HasilAnalisis::query(); - // filter destinasi jika dipilih if ($request->filled('destinasi')) { - $query->where('wisata', $request->destinasi); + $queryFilter->where('wisata', $request->destinasi); } - // ambil ulasan negatif - $negatif = $query->where('sentimen', 'negatif') - ->pluck('ulasan_terolah') + // --- 3. Hitung total & sentimen --- + $totalUlasan = $queryFilter->count(); + $totalNegatif = (clone $queryFilter)->where('sentimen', 'negatif')->count(); + $totalPositif = (clone $queryFilter)->where('sentimen', 'positif')->count(); + $totalNetral = (clone $queryFilter)->where('sentimen', 'netral')->count(); + + $persenNegatif = $totalUlasan > 0 ? round(($totalNegatif / $totalUlasan) * 100, 2) : 0; + $tingkatKepuasan = $totalUlasan > 0 ? round(($totalPositif / $totalUlasan) * 100) : 0; + + // Label kepuasan + $labelKepuasan = match(true) { + $tingkatKepuasan >= 80 => 'Baik', + $tingkatKepuasan >= 60 => 'Sedang', + default => 'Kurang', + }; + + // --- 4. Ambil ulasan negatif & bersihkan teks --- + $ulasanNegatif = (clone $queryFilter) + ->where('sentimen', 'negatif') + ->pluck('ulasan_bersih') // gunakan kolom yang sudah dipreproses ->toArray(); - // gabungkan semua ulasan jadi 1 string - $text = strtolower(implode(" ", $negatif)); + $text = strtolower(implode(' ', $ulasanNegatif)); + $words = array_filter(preg_split('/\s+/', $text)); - // token kata sederhana - $words = array_filter(explode(" ", $text)); + // --- 5. Rule-based: hitung skor tiap kategori isu --- + $issueSkor = []; + $issueWords = []; // kata dominan per kategori + + foreach ($this->issueRules as $kategori => $rule) { + $skor = 0; + $matchWords = []; + + foreach ($rule['keywords'] as $kw) { + $count = substr_count($text, $kw); + if ($count > 0) { + $skor += $count; + $matchWords[$kw] = $count; + } + } + + $issueSkor[$kategori] = $skor; + $issueWords[$kategori] = $matchWords; + } + + // Urutkan berdasarkan skor tertinggi + arsort($issueSkor); + + $totalSkorIsu = array_sum($issueSkor) ?: 1; // hindari division by zero + + // --- 6. Isu utama (top 5) dengan persentase --- + $isuUtama = []; + foreach (array_slice($issueSkor, 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'], + ]; + } + + // --- 7. Isu dominan (ranking 1) --- + $isuDominan = $isuUtama[0]['nama'] ?? 'Belum ada isu'; + $isuDominanPersen = $isuUtama[0]['persen'] ?? 0; + + // --- 8. Kata kunci dominan (global, top 10, filter stopword) --- + $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', + ]; - // hitung frekuensi kata $freq = array_count_values($words); + foreach ($stopwords as $sw) { + unset($freq[$sw]); + } + // filter kata pendek (< 3 karakter) + foreach (array_keys($freq) as $w) { + if (mb_strlen($w) < 3) unset($freq[$w]); + } - // urutkan kata paling sering arsort($freq); + $kataDominan = array_slice($freq, 0, 10, true); - // ambil top 5 keyword - $topKeywords = array_slice(array_keys($freq), 0, 5); + // --- 9. Prioritas rekomendasi (top 3 isu) --- + $prioritas = []; + $rank = 1; + foreach (array_slice($issueSkor, 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'], + ]; + } - // buat output card - $isuUtama = $topKeywords[0] ?? "Belum ada isu"; - $kataDominan = implode(", ", $topKeywords); + // --- 10. Saran perbaikan (top 3) --- + $saranPerbaikan = []; + foreach (array_slice($issueSkor, 0, 3, true) as $nama => $skor) { + $saranPerbaikan[] = [ + 'nama' => 'Perbaikan ' . $nama, + 'tip' => $this->issueRules[$nama]['saran']['tip'], + 'icon' => $this->issueRules[$nama]['icon'], + ]; + } - // saran otomatis sederhana - $saran = "Perlu peningkatan layanan terkait: " . $isuUtama; + // --- 11. Filter destinasi yang sedang aktif --- + $destinasiAktif = $request->input('destinasi', 'Semua Destinasi'); - $destinasiList = DB::table('hasil_analisis') - ->select('wisata') - ->distinct() - ->pluck('wisata'); - - - return view('dashboard', compact( - 'stats', - 'chartSentimen', - 'chartDestinasi', - 'totalPerWisata', - 'allText', - 'lastUpdate', - 'hasAnalisis', - 'wisataList', - 'hasil', - 'destinasiList' // โœ… TAMBAH INI -)); + return view('rekomendasi.index', compact( + 'destinasiList', + 'destinasiAktif', + 'totalUlasan', + 'totalNegatif', + 'totalPositif', + 'totalNetral', + 'persenNegatif', + 'tingkatKepuasan', + 'labelKepuasan', + 'isuUtama', + 'isuDominan', + 'isuDominanPersen', + 'kataDominan', + 'saranPerbaikan', + 'prioritas', + )); } -} +} \ No newline at end of file diff --git a/app/Http/Controllers/UlasanController.php b/app/Http/Controllers/UlasanController.php index 96b7a66..ec33dc0 100644 --- a/app/Http/Controllers/UlasanController.php +++ b/app/Http/Controllers/UlasanController.php @@ -12,6 +12,7 @@ class UlasanController extends Controller { public function index(Request $request) { + $periodeList = DB::table('periode_analisis')->orderBy('id', 'desc')->get(); $query = Ulasan::query(); // ๐Ÿ” SEARCH (ulasan + wisata) @@ -29,87 +30,116 @@ public function index(Request $request) $query->where('wisata', 'LIKE', '%' . $request->wisata . '%'); } - $mentah = $query->latest()->paginate(20)->withQueryString(); - - // OPTIONAL - $preprocessing = PreprocessingData::latest()->paginate(30, ['*'], 'pre_page'); - - // ๐Ÿ”ฅ RINGKASAN DATA - $totalUlasan = Ulasan::count(); - $totalWisata = Ulasan::distinct('wisata')->count('wisata'); - $lastUpdate = \App\Models\Ulasan::latest()->value('created_at'); - - return view('ulasan.index', compact( - 'mentah', - 'preprocessing', - 'totalUlasan', - 'totalWisata', - 'lastUpdate' - )); - } - - public function upload(Request $request) - { - $request->validate([ - 'file' => 'required|mimes:csv,txt,xlsx,xls|max:10240' - ]); - - $file = $request->file('file'); - $extension = strtolower($file->getClientOriginalExtension()); - - if (in_array($extension, ['xlsx', 'xls'])) { - - $data = Excel::toArray(null, $file); - $rows = $data[0] ?? []; - unset($rows[0]); - - foreach ($rows as $row) { - if (empty($row) || count($row) < 4) continue; - - DB::table('ulasan')->insert([ - 'wisata' => $row[0] ?? null, - 'rating' => isset($row[1]) ? (float) $row[1] : 0, - 'ulasan' => $row[2] ?? '', - 'tanggal' => isset($row[3]) ? date('Y-m-d H:i:s', strtotime($row[3])) : now(), - 'sentimen' => null, - 'reviewer' => 'anonymous', - 'created_at' => now(), - 'updated_at' => now(), - ]); - } - } else { - - $data = array_map(fn($line) => str_getcsv($line, ';'), file($file)); - unset($data[0]); - - foreach ($data as $row) { - if (empty($row) || count($row) < 4) continue; - - DB::table('ulasan')->insert([ - 'wisata' => $row[0] ?? null, - 'rating' => isset($row[1]) ? (float) $row[1] : 0, - 'ulasan' => $row[2] ?? '', - 'tanggal' => isset($row[3]) ? date('Y-m-d H:i:s', strtotime($row[3])) : now(), - 'sentimen' => null, - 'reviewer' => 'anonymous', - 'created_at' => now(), - 'updated_at' => now(), - ]); - } + // โ† filter periode + if ($request->filled('periode_id')) { + $query->where('periode_id', $request->periode_id); } - return redirect()->route('ulasan.index') - ->with('success', 'Upload berhasil dan data masuk!'); + $mentah = $query->orderBy('tanggal', 'desc')->paginate(20)->withQueryString(); + + // OPTIONAL + $preprocessing = PreprocessingData::latest()->paginate(20, ['*'], 'pre_page'); + + // ๐Ÿ”ฅ RINGKASAN DATA + + $totalUlasan = Ulasan::when($request->filled('periode_id'), fn($q) => $q->where('periode_id', $request->periode_id))->count(); + $totalWisata = Ulasan::when($request->filled('periode_id'), fn($q) => $q->where('periode_id', $request->periode_id))->distinct('wisata')->count('wisata'); + $lastUpdate = Ulasan::when($request->filled('periode_id'), fn($q) => $q->where('periode_id', $request->periode_id))->latest()->value('created_at'); + + return view('ulasan.index', compact( + 'mentah', + 'totalUlasan', + 'totalWisata', + 'lastUpdate', + 'periodeList' + )); } - public function analisisData() - { - $pythonPath = '"C:\\Users\\Mufrida Farah\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"'; - $scriptPath = '"C:\\laragon\\www\\Sentara\\scraper\\preprocessing.py"'; + public function upload(Request $request) +{ + $request->validate([ + 'file' => 'required|mimes:csv,txt,xlsx,xls|max:10240' + ]); - shell_exec($pythonPath . " " . $scriptPath . " 2>&1"); + // โœ… OTOMATIS BUAT / AMBIL PERIODE BULAN INI + $bulan = now()->month; + $tahun = now()->year; + $namaBulan = now()->translatedFormat('F Y'); // contoh: "Mei 2026" - return redirect()->route('ulasan.index') - ->with('success', 'Analisis berhasil dijalankan'); + $periode = DB::table('periode_analisis') + ->where('bulan', $bulan) + ->where('tahun', $tahun) + ->first(); + + if (!$periode) { + $periodeId = DB::table('periode_analisis')->insertGetId([ + 'nama' => $namaBulan, + 'bulan' => $bulan, + 'tahun' => $tahun, + 'created_at' => now(), + ]); + } else { + $periodeId = $periode->id; } + + // โœ… PROSES FILE + $file = $request->file('file'); + $extension = strtolower($file->getClientOriginalExtension()); + + if (in_array($extension, ['xlsx', 'xls'])) { + $data = Excel::toArray(null, $file); + $rows = $data[0] ?? []; + unset($rows[0]); + + foreach ($rows as $row) { + if (empty($row) || count($row) < 4) continue; + + DB::table('ulasan')->insert([ + 'wisata' => $row[0] ?? null, + 'rating' => isset($row[1]) ? (float) $row[1] : 0, + 'ulasan' => $row[2] ?? '', + 'tanggal' => isset($row[3]) ? date('Y-m-d H:i:s', strtotime($row[3])) : now(), + 'sentimen' => null, + 'reviewer' => 'anonymous', + 'periode_id' => $periodeId, // โ† tag periode + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + } else { + $data = array_map(fn($line) => str_getcsv($line, ';'), file($file)); + unset($data[0]); + + foreach ($data as $row) { + if (empty($row) || count($row) < 4) continue; + + DB::table('ulasan')->insert([ + 'wisata' => $row[0] ?? null, + 'rating' => isset($row[1]) ? (float) $row[1] : 0, + 'ulasan' => $row[2] ?? '', + 'tanggal' => isset($row[3]) ? date('Y-m-d H:i:s', strtotime($row[3])) : now(), + 'sentimen' => null, + 'reviewer' => 'anonymous', + 'periode_id' => $periodeId, // โ† tag periode + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + } + + return redirect()->route('ulasan.index') + ->with('success', 'Upload berhasil! Data masuk ke periode ' . $namaBulan); +} + + public function analisisData() +{ + $pythonPath = "C:\\Users\\Mufrida Farah\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"; + $scriptPath = base_path('scraper/analisis.py'); + + $command = "\"$pythonPath\" \"$scriptPath\" 2>&1"; + $output = shell_exec($command); + + return redirect()->route('analisis.index') + ->with('success', 'Analisis berhasil dijalankan'); +} } \ No newline at end of file diff --git a/app/Models/EvaluasiModel.php b/app/Models/EvaluasiModel.php new file mode 100644 index 0000000..1060119 --- /dev/null +++ b/app/Models/EvaluasiModel.php @@ -0,0 +1,21 @@ + 'UTC', + 'timezone' => 'Asia/Jakarta', /* |-------------------------------------------------------------------------- diff --git a/resources/views/analisis/index.blade.php b/resources/views/analisis/index.blade.php index 829a195..40e281a 100644 --- a/resources/views/analisis/index.blade.php +++ b/resources/views/analisis/index.blade.php @@ -8,9 +8,6 @@ Berikut adalah hasil evaluasi model dan detail analisis sentimen terhadap ulasan wisata.

- {{-- ================= METRICS ================= --}} @@ -19,28 +16,28 @@

Precision

- {{ $evaluasi->precision ?? 0 }} + {{ number_format($evaluasi->precision ?? 0, 2) }}

Recall

- {{ $evaluasi->recall ?? 0 }} + {{ number_format($evaluasi->recall ?? 0, 2) }}

F1 Score

- {{ $evaluasi->f1_score ?? 0 }} + {{ number_format($evaluasi->f1_score ?? 0, 2) }}

Akurasi

- {{ $evaluasi->accuracy ?? 0 }} + {{ number_format($evaluasi->accuracy ?? 0, 2) }}

@@ -66,8 +63,8 @@ Positif {{ $evaluasi->tp ?? 0 }} - {{ $evaluasi->fn ?? 0 }} - 0 + {{ $evaluasi->fp ?? 0 }} + {{ $evaluasi->fn ?? 0 }} Negatif @@ -96,29 +93,28 @@ Precision Recall F1 - Support Positif - {{ $evaluasi->precision ?? 0 }} - {{ $evaluasi->recall ?? 0 }} - {{ $evaluasi->f1_score ?? 0 }} + {{ number_format($evaluasi->precision ?? 0, 2) }} + {{ number_format($evaluasi->recall ?? 0, 2) }} + {{ number_format($evaluasi->f1_score ?? 0, 2) }} - Negatif - {{ $evaluasi->precision ?? 0 }} - {{ $evaluasi->recall ?? 0 }} - {{ $evaluasi->f1_score ?? 0 }} + {{ number_format($evaluasi->precision ?? 0, 2) }} + {{ number_format($evaluasi->recall ?? 0, 2) }} + {{ number_format($evaluasi->f1_score ?? 0, 2) }} - Netral - {{ $evaluasi->precision ?? 0 }} - {{ $evaluasi->recall ?? 0 }} - {{ $evaluasi->f1_score ?? 0 }} + {{ number_format($evaluasi->precision ?? 0, 2) }} + {{ number_format($evaluasi->recall ?? 0, 2) }} + {{ number_format($evaluasi->f1_score ?? 0, 2) }} - @@ -128,25 +124,23 @@ {{-- ================= FILTER ================= --}} -
+ - + - + + + + + + - @foreach($destinasiList as $d) - - @endforeach - + - - -
+ {{-- ================= TABLE ================= --}}
@@ -158,102 +152,100 @@ No Wisata - Ulasan + Ulasan Asli + Ulasan Bersih Sentimen Probabilitas - @forelse($hasil as $index => $item) + @forelse($hasil as $item) - {{ $loop->iteration }} + {{ ($hasil->currentPage() - 1) * $hasil->perPage() + $loop->iteration }} {{ $item->wisata }} - {{ Str::limit($item->ulasan_terolah, 80) }} - + {{ Str::limit($item->ulasan_asli ?? '-', 80) }} + {{ Str::limit($item->hasil_preprocessing ?? '-', 80) }} - sentimen) == 'positif' ? 'bg-green-100 text-green-600' : '' }} + {{ strtolower($item->sentimen) == 'negatif' ? 'bg-red-100 text-red-600' : '' }} + {{ strtolower($item->sentimen) == 'netral' ? 'bg-yellow-100 text-yellow-600' : '' }} "> - {{ $item->sentimen }} + {{ ucfirst($item->sentimen) }} - - {{ $item->probabilitas ?? '-' }} + {{ number_format($item->probabilitas ?? 0, 2) }} @empty - - Tidak ada data - + Tidak ada data @endforelse - {{-- PAGINATION --}} -
+ {{-- ================= PAGINATION ================= --}} + @if($hasil->hasPages()) + @php + $current = $hasil->currentPage(); + $last = $hasil->lastPage(); + $query = http_build_query(request()->except('page')); + // Window 2 halaman kiri & kanan dari current + $window = collect(range(max(1, $current - 2), min($last, $current + 2))); + @endphp - {{-- INFO --}} -
- Menampilkan {{ $hasil->firstItem() }} - {{ $hasil->lastItem() }} - dari {{ $hasil->total() }} data -
+
- {{-- PAGINATION --}} - {{-- PAGINATION --}} -
+ {{-- Info --}} +

Menampilkan {{ $hasil->firstItem() }} - {{ $hasil->lastItem() }} dari {{ $hasil->total() }} data

- {{-- PREV --}} - @if ($hasil->onFirstPage()) - โ€น - @else - โ€น - @endif + {{-- Tombol --}} +
- {{-- PAGE NUMBERS --}} - @foreach ($hasil->getUrlRange(1, $hasil->lastPage()) as $page => $url) + {{-- ยซ --}} + @if($hasil->onFirstPage()) + « + @else + « + @endif - @php - $query = request()->query(); - $query['page'] = $page; - $customUrl = url()->current() . '?' . http_build_query($query); - @endphp + {{-- Halaman 1 (jika tidak ada di window) --}} + @if(!$window->contains(1)) + 1 + @if($window->min() > 2) + ... + @endif + @endif - @if ($page == $hasil->currentPage()) - - {{ $page }} - + {{-- Window --}} + @foreach($window as $page) + @if($page == $current) + {{ $page }} + @else + {{ $page }} + @endif + @endforeach - @elseif ($page <= 3 || $page > $hasil->lastPage()-3 || abs($page - $hasil->currentPage()) <= 1) + {{-- Halaman terakhir (jika tidak ada di window) --}} + @if(!$window->contains($last)) + @if($window->max() < $last - 1) + ... + @endif + {{ $last }} + @endif - - {{ $page }} - + {{-- ยป --}} + @if($hasil->hasMorePages()) + » + @else + » + @endif - @elseif ($page == 4 || $page == $hasil->lastPage()-3) - ... +
+
@endif - @endforeach - - {{-- NEXT --}} - @if ($hasil->hasMorePages()) - โ€บ - @else - โ€บ - @endif - -
- -
-
\ No newline at end of file diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 0344742..9a0d68c 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -11,264 +11,243 @@ @endif -
- - -
- - - - - - - -
- -
- {{-- Statistik Cards --}} -
- -
-

Total Ulasan

-

- {{ $stats['total'] }} -

+ {{-- DROPDOWN PERIODE --}} +
+
+ Periode Analisis: +
+ + + + +
- -
-

Sentimen Positif

-

- {{ $stats['positif_persen'] }}% -

-
- -
-

Sentimen Negatif

-

- {{ $stats['negatif_persen'] }}% -

-
- -
-

Sentimen Netral

-

- {{ $stats['netral_persen'] }}% -

-
- + @if($periodeAktif) + + Data dianalisis: {{ \Carbon\Carbon::parse($periodeAktif->created_at)->format('d M Y') }} + + @endif
- {{-- Chart Section --}} -
+
- -
-

Distribusi Sentimen

-
- -
+ +
+ + +
- -
-

Sentimen per Destinasi

-
- -
-
+ +
-
- - {{-- Section Bawah --}} -
- - -
-

- Total Data per Destinasi -

- -
- @foreach($totalPerWisata as $item) -
- {{ $item->wisata }} - - {{ $item->total }} ulasan - -
- @endforeach + {{-- Statistik Cards --}} +
+
+

Total Ulasan

+

{{ $stats['total'] }}

+
+
+

Sentimen Positif

+

{{ $stats['positif_persen'] }}%

+
+
+

Sentimen Negatif

+

{{ $stats['negatif_persen'] }}%

+
+
+

Sentimen Netral

+

{{ $stats['netral_persen'] }}%

+
-
- -
-

- Word Cloud -

-
-
+ {{-- Chart Section --}} +
-
+ {{-- PIE CHART --}} +
+

Distribusi Sentimen

+
+ +
+
+ + {{-- BAR CHART - PURE HTML --}} + {{-- BAR CHART - PURE HTML VERTICAL --}} +
+

Sentimen per Destinasi

+ {{-- Legend --}} +
+ Positif + Negatif + Netral +
+ @php + $maxVal = 1; + foreach($chartDestinasi as $item) { + $maxVal = max($maxVal, $item->positif, $item->negatif, $item->netral); + } + @endphp +
+ @foreach($chartDestinasi as $item) + @php + $hPos = round(($item->positif / $maxVal) * 180); + $hNeg = round(($item->negatif / $maxVal) * 180); + $hNet = round(($item->netral / $maxVal) * 180); + $label = strlen($item->wisata) > 12 + ? substr($item->wisata, 0, 12) . '...' + : $item->wisata; + @endphp +
+
+ {{-- Positif --}} +
+ {{ $item->positif }} +
+ {{-- Negatif --}} +
+ {{ $item->negatif }} +
+ {{-- Netral --}} +
+ {{ $item->netral }} +
+
+

{{ $label }}

+
+ @endforeach
- - -
- @include('analisis.index')
- -
- @include('rekomendasi.index') +
+ + {{-- Section Bawah --}} +
+
+

Total Data per Destinasi

+
+ @foreach($totalPerWisata as $item) +
+ {{ $item->wisata }} + {{ $item->total }} ulasan +
+ @endforeach +
+
+
+

Word Cloud

+
+
+
+ +
+ + +
+ @include('analisis.index') +
+ + +
+ @include('rekomendasi.index') +
+ +
-
- - -{{-- ========================= --}} -{{-- SCRIPT SECTION --}} -{{-- ========================= --}} - +{{-- SCRIPTS --}} - - + @endsection \ No newline at end of file diff --git a/resources/views/rekomendasi/index.blade.php b/resources/views/rekomendasi/index.blade.php index e7ca28c..52037ef 100644 --- a/resources/views/rekomendasi/index.blade.php +++ b/resources/views/rekomendasi/index.blade.php @@ -1,3 +1,18 @@ +@php + $destinasiList ??= collect([]); + $totalNegatif ??= 0; + $totalUlasan ??= 0; + $persenNegatif ??= 0; + $tingkatKepuasan ??= 0; + $labelKepuasan ??= 'Kurang'; + $isuDominan ??= '-'; + $isuDominanPersen ??= 0; + $isuUtama ??= []; + $kataDominan ??= []; + $saranPerbaikan ??= []; + $prioritas ??= []; +@endphp +
{{-- HEADER --}} @@ -9,26 +24,29 @@

-
- + - - -
+
- {{-- SUMMARY --}} -
+ {{-- SUMMARY CARDS --}} +
๐Ÿ˜Ÿ

Total Ulasan Negatif

-

312

-

25,06% dari total

+

{{ $totalNegatif }}

+

{{ $persenNegatif }}% dari total

@@ -36,9 +54,12 @@
๐Ÿ˜Š

Tingkat Kepuasan

-

68%

- - Sedang +

{{ $tingkatKepuasan }}%

+ + {{ $labelKepuasan }}
@@ -47,20 +68,11 @@
โ—

Isu Dominan

-

Kebersihan

-

45% dari total isu

+

{{ $isuDominan }}

+

{{ $isuDominanPersen }}% dari total isu

- -
{{-- GRID ANALISIS --}} @@ -70,158 +82,120 @@

Isu Utama (Top 5)

- @php - $isu = [ - ['nama'=>'Kebersihan','persen'=>45,'warna'=>'red'], - ['nama'=>'Parkir','persen'=>30,'warna'=>'orange'], - ['nama'=>'Harga / Tiket','persen'=>15,'warna'=>'yellow'], - ['nama'=>'Fasilitas','persen'=>6,'warna'=>'green'], - ['nama'=>'Keramaian','persen'=>4,'warna'=>'blue'], - ]; - @endphp - - @foreach($isu as $i => $item) + @forelse ($isuUtama as $i => $isu) + @php + $warnaClass = match($isu['color']) { + 'red' => 'bg-red-500', + 'orange' => 'bg-orange-500', + 'yellow' => 'bg-yellow-400', + 'green' => 'bg-green-500', + default => 'bg-blue-500', + }; + @endphp
- {{ $i+1 }}. {{ $item['nama'] }} - {{ $item['persen'] }}% + {{ $i + 1 }}. {{ $isu['nama'] }} + {{ $isu['persen'] }}%
-
-
+
- @endforeach - + @empty +

Belum ada data isu.

+ @endforelse
- {{-- KEYWORD --}} + {{-- KATA KUNCI DOMINAN --}}

Kata Kunci Dominan

- @php - $keywords = [ - ['kotor',15], - ['parkir',10], - ['mahal',7], - ['sampah',6], - ['toilet',5], - ]; - @endphp + @php $maxFreq = !empty($kataDominan) ? max($kataDominan) : 1; @endphp - @foreach($keywords as $k) + @forelse (array_slice($kataDominan, 0, 5, true) as $kata => $jumlah)
- {{ $k[0] }} - {{ $k[1] }}x + {{ $kata }} + {{ $jumlah }}x
-
+
- @endforeach - + @empty +

Belum ada kata kunci.

+ @endforelse
- {{-- SARAN --}} + {{-- SARAN PERBAIKAN --}}

Saran Perbaikan

- -
-
๐Ÿงน
-
-

Perbaikan Kebersihan

-

- Tambah tempat sampah & jadwal pembersihan rutin. -

+ @forelse ($saranPerbaikan as $saran) +
+
{{ $saran['icon'] }}
+
+

{{ $saran['nama'] }}

+

{{ $saran['tip'] }}

+
-
- -
-
๐Ÿš—
-
-

Penataan Parkir

-

- Sistem parkir lebih rapi dan transparan. -

-
-
- -
-
๐Ÿท๏ธ
-
-

Evaluasi Harga

-

- Penyesuaian harga tiket agar lebih terjangkau. -

-
-
- + @empty +

Belum ada saran.

+ @endforelse
- {{-- PRIORITAS --}} + {{-- PRIORITAS REKOMENDASI --}}
- {{-- CARD 1 --}} -
-

Prioritas 1

-

Kebersihan

+ @forelse ($prioritas as $p) + @php + $border = match($p['color']) { + 'red' => 'border-red-300', + 'orange' => 'border-orange-300', + 'yellow' => 'border-yellow-300', + 'green' => 'border-green-300', + default => 'border-blue-300', + }; + $title = match($p['color']) { + 'red' => 'text-red-600', + 'orange' => 'text-orange-500', + 'yellow' => 'text-yellow-600', + 'green' => 'text-green-600', + default => 'text-blue-600', + }; + $dampak = match($p['color']) { + 'red' => 'bg-red-100 text-red-600', + 'orange' => 'bg-orange-100 text-orange-600', + 'yellow' => 'bg-yellow-100 text-yellow-600', + 'green' => 'bg-green-100 text-green-700', + default => 'bg-blue-100 text-blue-600', + }; + @endphp +
+

Prioritas {{ $p['rank'] }}

+

{{ $p['nama'] }}

-
    -
  • โœ” Tambah tempat sampah
  • -
  • โœ” Jadwal pembersihan rutin
  • -
  • โœ” Petugas kebersihan tambahan
  • -
+
    + @foreach ($p['actions'] as $action) +
  • + โœ” {{ $action }} +
  • + @endforeach +
-
- Dampak: Kepuasan meningkat signifikan +
+ Dampak: {{ $p['dampak'] }} +
-
- - {{-- CARD 2 --}} -
-

Prioritas 2

-

Parkir

- -
    -
  • โœ” Penataan area parkir
  • -
  • โœ” Tarif transparan
  • -
  • โœ” Petugas lebih ramah
  • -
- -
- Dampak: Mengurangi keluhan + @empty +
+ Belum ada data rekomendasi. Pastikan analisis sentimen sudah dijalankan.
-
- - {{-- CARD 3 --}} -
-

Prioritas 3

-

Harga

- -
    -
  • โœ” Evaluasi harga tiket
  • -
  • โœ” Promo wisata
  • -
  • โœ” Diskon tertentu
  • -
- -
- Dampak: Daya tarik meningkat -
-
+ @endforelse
diff --git a/resources/views/ulasan/index.blade.php b/resources/views/ulasan/index.blade.php index 9c3819c..553df47 100644 --- a/resources/views/ulasan/index.blade.php +++ b/resources/views/ulasan/index.blade.php @@ -5,68 +5,56 @@
-

Data Ulasan

+
+

Data Ulasan

+
+ +
+ @csrf + +
+
+
- - - -
+
+ +
-
-
- โ˜๏ธ -
+
โ˜๏ธ
- -

- Upload file CSV / XLSX -

- +

Upload file CSV / XLSX

klik tombol di bawah

-
@csrf - - - -
- - -
- -

- Format CSV yang Diperlukan -

- +

Format CSV yang Diperlukan

  • โœ” wisata, rating, ulasan, tanggal
  • โœ” Format: YYYY-MM-DD HH:MM:SS
  • โœ” Maks 10MB
-
-
๐Ÿ“„
@@ -74,7 +62,6 @@ class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm sha

{{ $totalUlasan }}

-
๐Ÿ“
@@ -82,7 +69,6 @@ class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm sha

{{ $totalWisata }}

-
๐Ÿ“…
@@ -92,11 +78,9 @@ class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm sha

-
-
@@ -106,154 +90,107 @@ class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm sha
@endif - - - -
+ + + {{-- DROPDOWN PERIODE --}} +
+ Periode: + +
+ + {{-- SEARCH --}} - - - - - - Reset - + Reset
- -
-
- - - - - - - - - - - - - - - @forelse($mentah as $i => $item) - - - - - - - - - - - - - - @empty - - - - @endforelse - - -
NoNama WisataRatingUlasanTanggal
{{ $mentah->firstItem() + $i }} - {{ $item->wisata }} - - {{ str_repeat('โ˜…', round($item->rating)) }} - {{ $item->rating }} - -
- {{ $item->ulasan }} -
-
- {{ $item->tanggal }} -
- Belum ada data -
- +
+ + + + + + + + + + + + @forelse($mentah as $i => $item) + + + + + + + + @empty + + + + @endforelse + +
NoNama WisataRatingUlasanTanggal
{{ $mentah->firstItem() + $i }}{{ $item->wisata }} + {{ str_repeat('โ˜…', round($item->rating)) }} + {{ $item->rating }} + +
{{ $item->ulasan }}
+
+ {{ \Carbon\Carbon::parse($item->tanggal)->diffForHumans() }} +
Belum ada data
+
+ Menampilkan {{ $mentah->firstItem() ?? 0 }} - {{ $mentah->lastItem() ?? 0 }} + dari {{ $mentah->total() }} data +
+
+ @if ($mentah->onFirstPage()) + ยซ + @else + ยซ + @endif - -
- Menampilkan {{ $mentah->firstItem() ?? 0 }} - {{ $mentah->lastItem() ?? 0 }} - dari {{ $mentah->total() }} data -
+ @for ($i = 1; $i <= $mentah->lastPage(); $i++) + @if ($i == $mentah->currentPage()) + {{ $i }} + @elseif ($i <= 3 || $i > $mentah->lastPage() - 2 || abs($i - $mentah->currentPage()) <= 1) + {{ $i }} + @elseif ($i == 4 || $i == $mentah->lastPage() - 3) + ... + @endif + @endfor - -
- - @if ($mentah->onFirstPage()) - ยซ - @else - ยซ - @endif - - @for ($i = 1; $i <= $mentah->lastPage(); $i++) - @if ($i == $mentah->currentPage()) - {{ $i }} - @elseif ($i <= 3 || $i > $mentah->lastPage() - 2 || abs($i - $mentah->currentPage()) <= 1) - {{ $i }} - @elseif ($i == 4 || $i == $mentah->lastPage() - 3) - ... - @endif - @endfor - - @if ($mentah->hasMorePages()) - ยป - @else - ยป - @endif - -
- -
+ @if ($mentah->hasMorePages()) + ยป + @else + ยป + @endif +
-
- -
-
- @csrf - -
-
- -
- -