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 ================= --}}
-
+
{{-- ================= 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
-
-
-
-
-
-
- Dashboard Sentara
-
-
-
- Hasil Analisis Sentimen
-
-
-
- Rekomendasi Layanan
-
-
-
-
-
- {{-- 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
-
-
-
+
+
+
+ Dashboard Sentara
+
+
+ Hasil Analisis Sentimen
+
+
+ Rekomendasi Layanan
+
-
-
-
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'] }}%
+
-
-
-
+ {{-- 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
+
+
+
+
+
+
+
+
+
+ @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 @@
-
-
- Semua Destinasi
+ {{-- FILTER DESTINASI โ submit ke /dashboard agar tab tidak reset --}}
+
+
- {{-- 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
+
+
+ Ambil Data Terbaru
+
+
+
+
-
-
-
-
+
+
+
-
-
-
- Upload file CSV / XLSX
-
-
+
Upload file CSV / XLSX
klik tombol di bawah
-
-
-
-
-
-
- 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
-
-
-
-
-
-
-
-
-
-
-
- No
- Nama Wisata
- Rating
- Ulasan
- Tanggal
-
-
-
-
-
- @forelse($mentah as $i => $item)
-
-
- {{ $mentah->firstItem() + $i }}
-
-
- {{ $item->wisata }}
-
-
-
- {{ str_repeat('โ
', round($item->rating)) }}
- {{ $item->rating }}
-
-
-
-
- {{ $item->ulasan }}
-
-
-
-
- {{ $item->tanggal }}
-
-
-
- @empty
-
-
- Belum ada data
-
-
- @endforelse
-
-
-
-
+
+
+
+
+ No
+ Nama Wisata
+ Rating
+ Ulasan
+ Tanggal
+
+
+
+ @forelse($mentah as $i => $item)
+
+ {{ $mentah->firstItem() + $i }}
+ {{ $item->wisata }}
+
+ {{ str_repeat('โ
', round($item->rating)) }}
+ {{ $item->rating }}
+
+
+ {{ $item->ulasan }}
+
+
+ {{ \Carbon\Carbon::parse($item->tanggal)->diffForHumans() }}
+
+
+ @empty
+
+ Belum ada data
+
+ @endforelse
+
+
+
+ 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
+
-
-
-
-
-
-
-
-
-