update fitur
This commit is contained in:
parent
5fa8005eb1
commit
dc393edbba
|
|
@ -10,16 +10,16 @@ class AnalisisController extends Controller
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
// Dropdown list destinasi unik
|
// Dropdown list destinasi unik
|
||||||
$destinasiList = HasilAnalisis::select('nama_wisata')
|
$wisataList = HasilAnalisis::select('wisata')
|
||||||
->distinct()
|
->distinct()
|
||||||
->pluck('nama_wisata');
|
->pluck('wisata');
|
||||||
|
|
||||||
// Query utama tabel
|
// Query utama tabel
|
||||||
$query = HasilAnalisis::query();
|
$query = HasilAnalisis::query();
|
||||||
|
|
||||||
// Filter kalau user pilih destinasi tertentu
|
// Filter kalau user pilih destinasi tertentu
|
||||||
if ($request->filled('destinasi')) {
|
if ($request->filled('wisata')) {
|
||||||
$query->where('nama_wisata', $request->destinasi);
|
$query->where('wisata', $request->wisata);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data hasil analisis (pakai paginate biar rapi)
|
// Data hasil analisis (pakai paginate biar rapi)
|
||||||
|
|
@ -27,7 +27,7 @@ public function index(Request $request)
|
||||||
|
|
||||||
return view('analisis.index', compact(
|
return view('analisis.index', compact(
|
||||||
'hasil',
|
'hasil',
|
||||||
'destinasiList'
|
'wisataList'
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,113 @@
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class DashboardController extends Controller
|
class DashboardController extends Controller
|
||||||
{
|
{
|
||||||
public function index()
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$stats = [
|
$wisata = $request->wisata;
|
||||||
'total' => 520,
|
|
||||||
'positif_persen' => 68,
|
|
||||||
'negatif_persen' => 20,
|
|
||||||
'netral_persen' => 12,
|
|
||||||
];
|
|
||||||
$lastUpdate = '27 Februari 2026, 22:45';
|
|
||||||
|
|
||||||
return view('dashboard', compact('stats', 'lastUpdate'));
|
// =============================
|
||||||
|
// TOTAL DATA
|
||||||
|
// =============================
|
||||||
|
$total = DB::table('hasil_analisis')
|
||||||
|
->when($wisata, function ($q) use ($wisata) {
|
||||||
|
$q->where('wisata', $wisata);
|
||||||
|
})
|
||||||
|
->count();
|
||||||
|
|
||||||
|
// =============================
|
||||||
|
// HITUNG SENTIMEN
|
||||||
|
// =============================
|
||||||
|
$positif = DB::table('hasil_analisis')
|
||||||
|
->where('sentimen', 'Positif')
|
||||||
|
->when($wisata, function ($q) use ($wisata) {
|
||||||
|
$q->where('wisata', $wisata);
|
||||||
|
})
|
||||||
|
->count();
|
||||||
|
|
||||||
|
$negatif = DB::table('hasil_analisis')
|
||||||
|
->where('sentimen', 'Negatif')
|
||||||
|
->when($wisata, function ($q) use ($wisata) {
|
||||||
|
$q->where('wisata', $wisata);
|
||||||
|
})
|
||||||
|
->count();
|
||||||
|
|
||||||
|
$netral = DB::table('hasil_analisis')
|
||||||
|
->where('sentimen', 'Netral')
|
||||||
|
->when($wisata, function ($q) use ($wisata) {
|
||||||
|
$q->where('wisata', $wisata);
|
||||||
|
})
|
||||||
|
->count();
|
||||||
|
|
||||||
|
// =============================
|
||||||
|
// PERSENTASE
|
||||||
|
// =============================
|
||||||
|
$stats = [
|
||||||
|
'total' => $total,
|
||||||
|
'positif_persen' => $total ? round(($positif / $total) * 100) : 0,
|
||||||
|
'negatif_persen' => $total ? round(($negatif / $total) * 100) : 0,
|
||||||
|
'netral_persen' => $total ? round(($netral / $total) * 100) : 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
// =============================
|
||||||
|
// GRAFIK PIE (Distribusi Sentimen)
|
||||||
|
// =============================
|
||||||
|
$chartSentimen = DB::table('hasil_analisis')
|
||||||
|
->select('sentimen', DB::raw('count(*) as total'))
|
||||||
|
->when($wisata, function ($q) use ($wisata) {
|
||||||
|
$q->where('wisata', $wisata);
|
||||||
|
})
|
||||||
|
->groupBy('sentimen')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
// =============================
|
||||||
|
// GRAFIK BAR (per destinasi)
|
||||||
|
// =============================
|
||||||
|
$chartDestinasi = 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")
|
||||||
|
)
|
||||||
|
->groupBy('wisata')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
// =============================
|
||||||
|
// TOTAL DATA PER DESTINASI
|
||||||
|
// =============================
|
||||||
|
$totalPerWisata = DB::table('hasil_analisis')
|
||||||
|
->select('wisata', DB::raw('count(*) as total'))
|
||||||
|
->groupBy('wisata')
|
||||||
|
->get()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
// =============================
|
||||||
|
// WORD CLOUD
|
||||||
|
// =============================
|
||||||
|
$texts = DB::table('preprocessing_data')
|
||||||
|
->pluck('stemming')
|
||||||
|
->toArray(); // 🔥 WAJIB
|
||||||
|
|
||||||
|
$allText = implode(' ', $texts);
|
||||||
|
|
||||||
|
// =============================
|
||||||
|
// LAST UPDATE
|
||||||
|
// =============================
|
||||||
|
$lastUpdate = DB::table('hasil_analisis')
|
||||||
|
->latest('created_at')
|
||||||
|
->value('created_at');
|
||||||
|
|
||||||
|
return view('dashboard', compact(
|
||||||
|
'stats',
|
||||||
|
'chartSentimen',
|
||||||
|
'chartDestinasi',
|
||||||
|
'totalPerWisata',
|
||||||
|
'allText',
|
||||||
|
'lastUpdate'
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,16 +10,16 @@ class RekomendasiController extends Controller
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
// dropdown destinasi unik
|
// dropdown destinasi unik
|
||||||
$destinasiList = HasilAnalisis::select('nama_wisata')
|
$destinasiList = HasilAnalisis::select('wisata')
|
||||||
->distinct()
|
->distinct()
|
||||||
->pluck('nama_wisata');
|
->pluck('scraping_pipeline.py');
|
||||||
|
|
||||||
// query utama
|
// query utama
|
||||||
$query = HasilAnalisis::query();
|
$query = HasilAnalisis::query();
|
||||||
|
|
||||||
// filter destinasi jika dipilih
|
// filter destinasi jika dipilih
|
||||||
if ($request->filled('destinasi')) {
|
if ($request->filled('destinasi')) {
|
||||||
$query->where('nama_wisata', $request->destinasi);
|
$query->where('wisata', $request->destinasi);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ambil ulasan negatif
|
// ambil ulasan negatif
|
||||||
|
|
|
||||||
|
|
@ -4,58 +4,51 @@
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Models\Ulasan;
|
use App\Models\Ulasan;
|
||||||
// use App\Models\HasilSentimen;
|
|
||||||
use App\Models\PreprocessingData;
|
use App\Models\PreprocessingData;
|
||||||
|
|
||||||
|
|
||||||
class UlasanController extends Controller
|
class UlasanController extends Controller
|
||||||
{
|
{
|
||||||
// public function index()
|
public function index(Request $request)
|
||||||
// {
|
{
|
||||||
// $mentah = Ulasan::where('is_processed', 0)->get();
|
$wisata = $request->wisata;
|
||||||
// // $hasil = HasilSentimen::latest()->get();
|
|
||||||
|
|
||||||
// // return view('ulasan.index', compact('mentah', 'hasil'));
|
$query = Ulasan::query();
|
||||||
// }
|
if ($request->filled('search')) {
|
||||||
|
$query->where('ulasan', 'LIKE', '%' . $request->search . '%');
|
||||||
public function index()
|
|
||||||
{
|
|
||||||
// Data ulasan mentah (belum diproses)
|
|
||||||
$mentah = Ulasan::all();
|
|
||||||
|
|
||||||
// Data hasil preprocessing
|
|
||||||
$preprocessing = PreprocessingData::latest()->get();
|
|
||||||
|
|
||||||
return view('ulasan.index', compact('mentah', 'preprocessing'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($wisata) {
|
||||||
|
$query->where('wisata', 'LIKE', '%' . $wisata . '%');
|
||||||
|
}
|
||||||
|
|
||||||
// Tombol ambil data terbaru
|
// ✅ PAGINATION 50 DATA
|
||||||
public function ambilData()
|
$mentah = $query->latest()->paginate(50)->withQueryString();
|
||||||
{
|
|
||||||
$pythonPath = '"C:\\Users\\Mufrida Farah\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"';
|
|
||||||
$scriptPath = base_path('scraper/scraping_pipeline.py');
|
|
||||||
|
|
||||||
$command = $pythonPath . " " . $scriptPath . " 2>&1";
|
// preprocessing
|
||||||
|
$preprocessing = PreprocessingData::latest()->paginate(50);
|
||||||
|
|
||||||
shell_exec($command);
|
return view('ulasan.index', compact('mentah', 'preprocessing'));
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->route('ulasan.index')
|
public function ambilData()
|
||||||
->with('success', 'Data berhasil diambil dari Google Maps');
|
{
|
||||||
}
|
$pythonPath = "C:\\Users\\Mufrida Farah\\AppData\\Local\\Programs\\Python\\Python312\\python.exe";
|
||||||
|
$scriptPath = base_path('scraper/scraping_pipeline.py');
|
||||||
|
|
||||||
|
shell_exec($pythonPath . " " . $scriptPath);
|
||||||
|
|
||||||
|
return redirect()->route('ulasan.index')
|
||||||
|
->with('success','Data berhasil diambil');
|
||||||
|
}
|
||||||
|
|
||||||
// Tombol analisis data
|
|
||||||
public function analisisData()
|
public function analisisData()
|
||||||
{
|
{
|
||||||
$pythonPath = '"C:\\Users\\Mufrida Farah\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"';
|
$pythonPath = '"C:\\Users\\Mufrida Farah\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"';
|
||||||
|
$scriptPath = '"C:\\laragon\\www\\Sentara\\scraper\\preprocessing.py"';
|
||||||
|
|
||||||
$scriptPath = base_path('scraper/preprocessing.py');
|
shell_exec($pythonPath . " " . $scriptPath . " 2>&1");
|
||||||
|
|
||||||
$command = $pythonPath . " " . $scriptPath . " 2>&1";
|
return redirect()->route('ulasan.index')
|
||||||
|
->with('success','Analisis berhasil');
|
||||||
shell_exec($command);
|
}
|
||||||
|
|
||||||
return redirect()->route('ulasan.index')
|
|
||||||
->with('success','Preprocessing berhasil dilakukan');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -12,7 +12,7 @@ class HasilAnalisis extends Model
|
||||||
protected $table = 'hasil_analisis';
|
protected $table = 'hasil_analisis';
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'nama_wisata',
|
'wisata',
|
||||||
'ulasan_terolah',
|
'ulasan_terolah',
|
||||||
'sentimen',
|
'sentimen',
|
||||||
'probabilitas'
|
'probabilitas'
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class HasilSentimen extends Model
|
||||||
protected $table = 'hasil_sentimen';
|
protected $table = 'hasil_sentimen';
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'nama_wisata',
|
'wisata',
|
||||||
'ulasan',
|
'ulasan',
|
||||||
'sentimen'
|
'sentimen'
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class Ulasan extends Model
|
||||||
protected $table = 'ulasan';
|
protected $table = 'ulasan';
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'nama_wisata',
|
'wisata',
|
||||||
'reviewer',
|
'reviewer',
|
||||||
'rating',
|
'rating',
|
||||||
'ulasan',
|
'ulasan',
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('ulasan_mentah', function (Blueprint $table) {
|
Schema::create('ulasan_mentah', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('nama_wisata');
|
$table->string('wisata');
|
||||||
$table->string('reviewer_name')->nullable();
|
$table->string('reviewer_name')->nullable();
|
||||||
$table->integer('rating')->nullable();
|
$table->integer('rating')->nullable();
|
||||||
$table->text('ulasan');
|
$table->text('ulasan');
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('hasil_analisis', function (Blueprint $table) {
|
Schema::create('hasil_analisis', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('nama_wisata');
|
$table->string('wisata');
|
||||||
$table->text('ulasan_terolah');
|
$table->text('ulasan_terolah');
|
||||||
$table->string('sentimen');
|
$table->string('sentimen');
|
||||||
$table->float('probabilitas');
|
$table->float('probabilitas');
|
||||||
|
|
|
||||||
|
|
@ -26,3 +26,10 @@ .sidebar-menu .sidebar-link {
|
||||||
.sidebar-menu .sidebar-link:hover {
|
.sidebar-menu .sidebar-link:hover {
|
||||||
background: rgba(255, 255, 255, 0.15);
|
background: rgba(255, 255, 255, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.line-clamp-3 {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
@ -12,16 +12,16 @@
|
||||||
|
|
||||||
<!-- Dropdown Filter -->
|
<!-- Dropdown Filter -->
|
||||||
<form method="GET" action="{{ route('analisis.index') }}">
|
<form method="GET" action="{{ route('analisis.index') }}">
|
||||||
<select name="destinasi"
|
<select name="wisata"
|
||||||
onchange="this.form.submit()"
|
onchange="this.form.submit()"
|
||||||
class="bg-white border border-gray-300 text-sm rounded-lg px-4 py-2 shadow-sm
|
class="bg-white border border-gray-300 text-sm rounded-lg px-4 py-2 shadow-sm
|
||||||
focus:ring-2 focus:ring-blue-400 focus:outline-none">
|
focus:ring-2 focus:ring-blue-400 focus:outline-none">
|
||||||
|
|
||||||
<option value="">Semua Destinasi</option>
|
<option value="">Semua Destinasi</option>
|
||||||
|
|
||||||
@foreach($destinasiList as $dest)
|
@foreach($wisataList as $dest)
|
||||||
<option value="{{ $dest }}"
|
<option value="{{ $dest }}"
|
||||||
{{ request('destinasi') == $dest ? 'selected' : '' }}>
|
{{ request('wisata') == $dest ? 'selected' : '' }}>
|
||||||
{{ $dest }}
|
{{ $dest }}
|
||||||
</option>
|
</option>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
@ -41,7 +41,7 @@ class="bg-white border border-gray-300 text-sm rounded-lg px-4 py-2 shadow-sm
|
||||||
<table class="w-full text-sm text-left">
|
<table class="w-full text-sm text-left">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-gray-100 text-gray-600">
|
<tr class="bg-gray-100 text-gray-600">
|
||||||
<th class="px-4 py-3">Destinasi</th>
|
<th class="px-4 py-3">wisata</th>
|
||||||
<th class="px-4 py-3">Ulasan Terolah</th>
|
<th class="px-4 py-3">Ulasan Terolah</th>
|
||||||
<th class="px-4 py-3">Sentimen</th>
|
<th class="px-4 py-3">Sentimen</th>
|
||||||
<th class="px-4 py-3">Probabilitas</th>
|
<th class="px-4 py-3">Probabilitas</th>
|
||||||
|
|
@ -54,7 +54,7 @@ class="bg-white border border-gray-300 text-sm rounded-lg px-4 py-2 shadow-sm
|
||||||
|
|
||||||
<!-- Destinasi -->
|
<!-- Destinasi -->
|
||||||
<td class="px-4 py-3 font-medium">
|
<td class="px-4 py-3 font-medium">
|
||||||
{{ $row->nama_wisata }}
|
{{ $row->wisata }}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- Ulasan -->
|
<!-- Ulasan -->
|
||||||
|
|
|
||||||
|
|
@ -2,147 +2,145 @@
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
<div class="max-w-7xl mx-auto">
|
<div class="max-w-6xl mx-auto space-y-8">
|
||||||
|
|
||||||
{{-- Header --}}
|
<!-- HEADER -->
|
||||||
<div class="flex justify-between items-start mb-8">
|
<div class="flex justify-between items-center">
|
||||||
<div>
|
<h2 class="text-3xl font-bold text-gray-800">Data Ulasan</h2>
|
||||||
<h2 class="text-2xl font-semibold text-gray-800">
|
|
||||||
Dashboard
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<!-- <p class="text-sm text-gray-500 mt-1">
|
|
||||||
Terakhir diperbarui: 27 Februari 2026, 22:45
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p class="text-xs text-gray-400">
|
|
||||||
Periode Data: Februari 2026
|
|
||||||
</p> -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Filter Destinasi -->
|
|
||||||
<form method="GET" action="" class="ml-4">
|
|
||||||
<select name="destinasi"
|
|
||||||
class="bg-white border border-gray-300 text-sm rounded-lg px-4 py-2 shadow-sm
|
|
||||||
focus:ring-2 focus:ring-blue-400 focus:outline-none">
|
|
||||||
|
|
||||||
|
<select id="filterWisata" class="border rounded px-3 py-2 text-sm">
|
||||||
<option value="">Semua Destinasi</option>
|
<option value="">Semua Destinasi</option>
|
||||||
<option value="papuma">Pantai Papuma</option>
|
<option value="Pantai Papuma" {{ request('wisata')=='Pantai Papuma'?'selected':'' }}>Pantai Papuma</option>
|
||||||
<option value="watuulo">Pantai Watu Ulo</option>
|
<option value="Pantai Watu Ulo" {{ request('wisata')=='Pantai Watu Ulo'?'selected':'' }}>Pantai Watu Ulo</option>
|
||||||
<option value="teluklove">Teluk Love (Payangan)</option>
|
<option value="Teluk Love (Payangan)" {{ request('wisata')=='Teluk Love (Payangan)'?'selected':'' }}>Teluk Love</option>
|
||||||
<option value="gununggambir">Kebun Teh Gunung Gambir </option>
|
<option value="Kebun Teh Gunung Gambir" {{ request('wisata')=='Kebun Teh Gunung Gambir'?'selected':'' }}>Kebun Teh Gunung Gambir</option>
|
||||||
|
|
||||||
</select>
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById("filterWisata").addEventListener("change", function () {
|
||||||
|
window.location.href = "/ulasan?wisata=" + encodeURIComponent(this.value);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- BUTTON -->
|
||||||
|
<form action="{{ route('ulasan.ambil') }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
<button class="bg-blue-600 text-white px-5 py-2 rounded">Ambil Data Terbaru</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Statistik Cards --}}
|
<!-- ================= TABLE ================= -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
|
|
||||||
|
|
||||||
<div class="bg-white rounded-xl shadow p-5">
|
|
||||||
<p class="text-gray-500 text-sm">Total Ulasan</p>
|
|
||||||
<h3 class="text-2xl font-bold mt-2 text-gray-900">
|
|
||||||
{{ $stats['total'] }}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-xl shadow p-5">
|
|
||||||
<p class="text-gray-500 text-sm">Sentimen Positif</p>
|
|
||||||
<h3 class="text-2xl font-bold mt-2 text-green-600">
|
|
||||||
{{ $stats['positif_persen'] }}%
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-xl shadow p-5">
|
|
||||||
<p class="text-gray-500 text-sm">Sentimen Negatif</p>
|
|
||||||
<h3 class="text-2xl font-bold mt-2 text-red-600">
|
|
||||||
{{ $stats['negatif_persen'] }}%
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white rounded-xl shadow p-5">
|
|
||||||
<p class="text-gray-500 text-sm">Sentimen Netral</p>
|
|
||||||
<h3 class="text-2xl font-bold mt-2 text-yellow-500">
|
|
||||||
{{ $stats['netral_persen'] }}%
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Chart Section --}}
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
|
||||||
|
|
||||||
<!-- Pie Chart -->
|
|
||||||
<div class="bg-white rounded-xl shadow p-6">
|
<div class="bg-white rounded-xl shadow p-6">
|
||||||
<h3 class="font-semibold mb-4 text-gray-700">
|
|
||||||
Distribusi Sentimen
|
<h3 class="font-semibold mb-4">Data Ulasan Mentah</h3>
|
||||||
</h3>
|
|
||||||
<div class="h-[280px] flex justify-center items-center">
|
<div class="border rounded overflow-hidden">
|
||||||
<canvas id="pieChart"></canvas>
|
|
||||||
|
<!-- HEADER -->
|
||||||
|
<table class="w-full table-fixed text-sm">
|
||||||
|
<thead class="bg-gray-100">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-3 w-1/4">Wisata</th>
|
||||||
|
<th class="px-4 py-3 w-1/6">Rating</th>
|
||||||
|
<th class="px-4 py-3 w-2/4">Ulasan</th>
|
||||||
|
<th class="px-4 py-3 w-1/6">Tanggal</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- BODY SCROLL -->
|
||||||
|
<div class="max-h-[300px] overflow-y-auto">
|
||||||
|
<table class="w-full table-fixed text-sm">
|
||||||
|
<tbody>
|
||||||
|
@forelse($mentah as $item)
|
||||||
|
<tr class="border-b hover:bg-gray-50">
|
||||||
|
<td class="px-4 py-2">{{ $item->wisata }}</td>
|
||||||
|
<td class="px-4 py-2">{{ $item->rating }}</td>
|
||||||
|
<td class="px-4 py-2 break-words line-clamp-3">
|
||||||
|
{{ $item->ulasan }}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-2">{{ $item->tanggal }}</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-center py-4 text-gray-400">
|
||||||
|
Tidak ada data
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- PAGINATION SIMPLE -->
|
||||||
|
<div class="flex justify-center mt-6 gap-2 text-sm">
|
||||||
|
|
||||||
|
@if($mentah->onFirstPage())
|
||||||
|
<span class="text-gray-400">Prev</span>
|
||||||
|
@else
|
||||||
|
<a href="{{ $mentah->previousPageUrl() }}">Prev</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@for ($i = 1; $i <= $mentah->lastPage(); $i++)
|
||||||
|
@if ($i == $mentah->currentPage())
|
||||||
|
<span class="font-bold text-blue-600">{{ $i }}</span>
|
||||||
|
@elseif ($i <= 5 || $i == $mentah->lastPage())
|
||||||
|
<a href="{{ $mentah->url($i) }}">{{ $i }}</a>
|
||||||
|
@elseif ($i == 6)
|
||||||
|
<span>...</span>
|
||||||
|
@endif
|
||||||
|
@endfor
|
||||||
|
|
||||||
|
@if($mentah->hasMorePages())
|
||||||
|
<a href="{{ $mentah->nextPageUrl() }}">Next</a>
|
||||||
|
@else
|
||||||
|
<span class="text-gray-400">Next</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bar Chart -->
|
<!-- BUTTON ANALISIS -->
|
||||||
|
<form action="{{ route('ulasan.analisis') }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
<button class="bg-green-600 text-white px-5 py-2 rounded">Proses Analisis</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- PREPROCESSING -->
|
||||||
<div class="bg-white rounded-xl shadow p-6">
|
<div class="bg-white rounded-xl shadow p-6">
|
||||||
<h3 class="font-semibold mb-4 text-gray-700">
|
<h3 class="font-semibold mb-4">Hasil Preprocessing</h3>
|
||||||
Sentimen per Destinasi
|
|
||||||
</h3>
|
<div class="max-h-[300px] overflow-y-auto">
|
||||||
<div class="h-[280px]">
|
<table class="w-full text-sm">
|
||||||
<canvas id="barChart"></canvas>
|
<thead class="bg-gray-100 sticky top-0">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-2">Wisata</th>
|
||||||
|
<th class="px-4 py-2">Ulasan Mentah</th>
|
||||||
|
<th class="px-4 py-2">Ulasan Bersih</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($preprocessing as $row)
|
||||||
|
<tr class="border-b">
|
||||||
|
<td class="px-4 py-2">{{ $row->wisata }}</td>
|
||||||
|
<td class="px-4 py-2">{{ $row->ulasan_asli }}</td>
|
||||||
|
<td class="px-4 py-2">{{ $row->stemming }}</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="text-center py-4 text-gray-400">
|
||||||
|
Belum ada data
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div> {{-- TUTUP GRID CHART --}}
|
|
||||||
|
|
||||||
|
|
||||||
{{-- Section Bawah --}}
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
||||||
|
|
||||||
<!-- Total Data per Destinasi -->
|
|
||||||
<div class="bg-white rounded-xl shadow p-6 h-[320px]">
|
|
||||||
<h3 class="font-semibold mb-6 text-gray-700">
|
|
||||||
Total Data per Destinasi
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div class="space-y-4 text-sm text-gray-600">
|
|
||||||
<div class="flex justify-between border-b pb-3">
|
|
||||||
<span>Pantai Papuma</span>
|
|
||||||
<span class="font-semibold text-gray-800">120 ulasan</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between border-b pb-3">
|
|
||||||
<span>Pantai Watu Ulo</span>
|
|
||||||
<span class="font-semibold text-gray-800">85 ulasan</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between border-b pb-3">
|
|
||||||
<span>Teluk Love (Payangan)</span>
|
|
||||||
<span class="font-semibold text-gray-800">95 ulasan</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-between">
|
|
||||||
<span>Kebun Teh Gunung Gambir</span>
|
|
||||||
<span class="font-semibold text-gray-800">60 ulasan</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tren Sentimen per Bulan -->
|
|
||||||
<!-- <div class="bg-white rounded-xl shadow p-6 h-[320px]">
|
|
||||||
<h3 class="font-semibold mb-6 text-gray-700">
|
|
||||||
Tren Sentimen per Bulan
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div class="h-[220px] flex items-center justify-center text-gray-400">
|
|
||||||
Grafik tren akan ditampilkan setelah data periode tersedia.
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
@ -6,132 +6,246 @@
|
||||||
|
|
||||||
<!-- HEADER -->
|
<!-- HEADER -->
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<h2 class="text-3xl font-bold text-gray-800">
|
<h2 class="text-3xl font-bold text-gray-800">Data Ulasan</h2>
|
||||||
Data Ulasan
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<select
|
<select id="filterWisata" class="border rounded px-3 py-2 text-sm">
|
||||||
class="bg-white border border-gray-300 text-sm rounded-lg px-4 py-2 shadow-sm
|
<option value="">Semua Destinasi</option>
|
||||||
focus:ring-2 focus:ring-blue-400 focus:outline-none">
|
<option value="Pantai Papuma" {{ request('wisata')=='Pantai Papuma'?'selected':'' }}>Pantai Papuma</option>
|
||||||
<option>Semua Destinasi</option>
|
<option value="Pantai Watu Ulo" {{ request('wisata')=='Pantai Watu Ulo'?'selected':'' }}>Pantai Watu Ulo</option>
|
||||||
<option>Pantai Papuma</option>
|
<option value="Teluk Love (Payangan)" {{ request('wisata')=='Teluk Love (Payangan)'?'selected':'' }}>Teluk Love</option>
|
||||||
<option>Pantai Watu Ulo</option>
|
<option value="Kebun Teh Gunung Gambir" {{ request('wisata')=='Kebun Teh Gunung Gambir'?'selected':'' }}>Kebun Teh Gunung Gambir</option>
|
||||||
<option>Teluk Love (Payangan)</option>
|
|
||||||
<option>Kebun Teh Gunung Gambir </option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- BUTTON AMBIL DATA -->
|
<script>
|
||||||
<div>
|
document.getElementById("filterWisata").addEventListener("change", function () {
|
||||||
<form action="{{ route('ulasan.ambil') }}" method="POST">
|
window.location.href = "/ulasan?wisata=" + encodeURIComponent(this.value);
|
||||||
@csrf
|
});
|
||||||
<button
|
</script>
|
||||||
class="bg-blue-600 hover:bg-blue-700 text-white font-semibold
|
|
||||||
px-6 py-2 rounded-lg shadow">
|
<!-- BUTTON -->
|
||||||
Ambil Data Terbaru
|
<form action="{{ route('ulasan.ambil') }}" method="POST">
|
||||||
</button>
|
@csrf
|
||||||
</form>
|
<button class="bg-blue-600 text-white px-5 py-2 rounded">
|
||||||
|
Ambil Data Terbaru
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- ================= TABLE ================= -->
|
||||||
|
<div class="bg-white rounded-xl shadow p-6">
|
||||||
|
|
||||||
|
<h3 class="font-semibold mb-4">Data Ulasan Mentah</h3>
|
||||||
|
|
||||||
|
<div class="border rounded overflow-hidden">
|
||||||
|
|
||||||
|
<!-- HEADER -->
|
||||||
|
<table class="w-full text-sm text-left table-fixed border border-gray-200 rounded-t-lg overflow-hidden">
|
||||||
|
<thead class="bg-gray-100 text-gray-700">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-3 w-[15%]">Wisata</th>
|
||||||
|
<th class="px-4 py-3 w-[10%] text-center">Rating</th>
|
||||||
|
<th class="px-4 py-3 w-[60%]">Ulasan</th>
|
||||||
|
<th class="px-4 py-3 w-[15%] text-center">Tanggal</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- BODY -->
|
||||||
|
<div class="max-h-[300px] overflow-y-auto border-x border-b border-gray-200 rounded-b-lg">
|
||||||
|
<table class="w-full text-sm table-fixed">
|
||||||
|
|
||||||
|
<tbody class="divide-y divide-gray-200">
|
||||||
|
|
||||||
|
@foreach($mentah as $item)
|
||||||
|
<tr class="hover:bg-gray-50 transition">
|
||||||
|
|
||||||
|
<!-- WISATA -->
|
||||||
|
<td class="px-4 py-3 w-[15%] font-medium text-gray-700">
|
||||||
|
{{ $item->wisata }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- RATING -->
|
||||||
|
<td class="px-4 py-3 w-[10%] text-center">
|
||||||
|
{{ $item->rating }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- ULASAN -->
|
||||||
|
<td class="px-4 py-3 w-[60%] text-black-700 leading-relaxed">
|
||||||
|
<div class="line-clamp-3">
|
||||||
|
{{ $item->ulasan }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- TANGGAL -->
|
||||||
|
<td class="px-4 py-3 w-[15%] text-center text-black-700 leading-relaxed">
|
||||||
|
{{ $item->tanggal }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TABLE DATA MENTAH -->
|
</div>
|
||||||
|
|
||||||
|
<!-- INFO -->
|
||||||
|
<div class="text-sm text-gray-500 mt-4 text-center">
|
||||||
|
Showing {{ $mentah->firstItem() }} to {{ $mentah->lastItem() }}
|
||||||
|
of {{ $mentah->total() }} results
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PAGINATION -->
|
||||||
|
<div class="flex justify-center mt-6 items-center gap-2 text-sm">
|
||||||
|
|
||||||
|
{{-- FIRST --}}
|
||||||
|
@if ($mentah->currentPage() > 1)
|
||||||
|
<a href="{{ $mentah->url(1) }}">«</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- PREV --}}
|
||||||
|
@if ($mentah->onFirstPage())
|
||||||
|
<span class="text-gray-400">‹</span>
|
||||||
|
@else
|
||||||
|
<a href="{{ $mentah->previousPageUrl() }}">‹</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@php
|
||||||
|
$start = max($mentah->currentPage() - 2, 1);
|
||||||
|
$end = min($mentah->currentPage() + 2, $mentah->lastPage());
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
{{-- NUMBER DINAMIS --}}
|
||||||
|
@for ($i = $start; $i <= $end; $i++)
|
||||||
|
@if ($i == $mentah->currentPage())
|
||||||
|
<span class="px-3 py-1 bg-blue-600 text-white rounded-full">{{ $i }}</span>
|
||||||
|
@else
|
||||||
|
<a href="{{ $mentah->url($i) }}" class="px-3 py-1 hover:underline">
|
||||||
|
{{ $i }}
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
@endfor
|
||||||
|
|
||||||
|
{{-- NEXT --}}
|
||||||
|
@if ($mentah->hasMorePages())
|
||||||
|
<a href="{{ $mentah->nextPageUrl() }}">›</a>
|
||||||
|
@else
|
||||||
|
<span class="text-gray-400">›</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- LAST --}}
|
||||||
|
@if ($mentah->currentPage() < $mentah->lastPage())
|
||||||
|
<a href="{{ $mentah->url($mentah->lastPage()) }}">»</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BUTTON ANALISIS -->
|
||||||
|
<form action="{{ route('ulasan.analisis') }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
<button class="bg-green-600 text-white px-5 py-2 rounded">
|
||||||
|
Proses Analisis
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- PREPROCESSING -->
|
||||||
<div class="bg-white rounded-xl shadow p-6">
|
<div class="bg-white rounded-xl shadow p-6">
|
||||||
<h3 class="text-lg font-semibold text-gray-700 mb-4">
|
<h3 class="font-semibold mb-4">Hasil Preprocessing</h3>
|
||||||
Data Ulasan Mentah
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div class="overflow-x-auto max-h-[400px] overflow-y-auto">
|
|
||||||
<table class="w-full text-sm text-left">
|
|
||||||
<thead>
|
|
||||||
<tr class="bg-gray-100 text-gray-600">
|
|
||||||
<th class="px-4 py-3">Destinasi</th>
|
|
||||||
<th class="px-4 py-3">Reviewer</th>
|
|
||||||
<th class="px-4 py-3">Rating</th>
|
|
||||||
<th class="px-4 py-3">Ulasan</th>
|
|
||||||
<th class="px-4 py-3">Tanggal</th>
|
|
||||||
|
|
||||||
|
<div class="max-h-[300px] overflow-y-auto">
|
||||||
|
<table class="w-full text-sm">
|
||||||
|
<thead class="bg-gray-100 sticky top-0">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-2">Wisata</th>
|
||||||
|
<th class="px-4 py-2">Ulasan Mentah</th>
|
||||||
|
<th class="px-4 py-2">Ulasan Bersih</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
@forelse($mentah as $data)
|
@forelse($preprocessing as $row)
|
||||||
<tr>
|
<tr class="border-b">
|
||||||
<td class="px-4 py-3">{{ $data->destinasi }}</td>
|
<td class="px-4 py-2">{{ $row->wisata }}</td>
|
||||||
<td class="px-4 py-3">{{ $data->reviewer }}</td>
|
<td class="px-4 py-2">{{ $row->ulasan_asli }}</td>
|
||||||
<td class="px-4 py-3">{{ $data->rating }}</td>
|
<td class="px-4 py-2">{{ $row->stemming }}</td>
|
||||||
<td class="px-4 py-3">{{ $data->ulasan }}</td>
|
</tr>
|
||||||
<td class="px-4 py-3">{{ $data->tanggal }}</td>
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td colspan="3" class="text-center py-4 text-gray-400">
|
||||||
|
Belum ada data
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<div class="flex justify-center mt-4 text-sm">
|
||||||
|
|
||||||
</tr>
|
<!-- INFO -->
|
||||||
@empty
|
<div class="text-sm text-gray-500 mt-4 text-center">
|
||||||
<tr>
|
Showing {{ $preprocessing->firstItem() }} to {{ $preprocessing->lastItem() }}
|
||||||
<td colspan="6" class="text-center py-6 text-gray-400">
|
of {{ $preprocessing->total() }} results
|
||||||
Belum ada data mentah.
|
</div>
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforelse
|
|
||||||
</tbody>
|
|
||||||
|
|
||||||
</table>
|
<!-- PAGINATION -->
|
||||||
</div>
|
<div class="flex justify-center mt-6 items-center gap-2 text-sm">
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- BUTTON PREPROCESSING -->
|
{{-- FIRST --}}
|
||||||
<div>
|
@if ($preprocessing->currentPage() > 1)
|
||||||
<form action="{{ route('ulasan.analisis') }}" method="POST">
|
<a href="{{ $preprocessing->url(1) }}">«</a>
|
||||||
@csrf
|
@endif
|
||||||
<button
|
|
||||||
class="bg-green-600 hover:bg-green-700 text-white font-semibold
|
|
||||||
px-6 py-2 rounded-lg shadow">
|
|
||||||
Proses Analisis
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- HASIL PREPROCESSING -->
|
{{-- PREV --}}
|
||||||
<div class="bg-white rounded-xl shadow p-6">
|
@if ($preprocessing->onFirstPage())
|
||||||
<h3 class="text-lg font-semibold text-gray-700 mb-4">
|
<span class="text-gray-400">‹</span>
|
||||||
Hasil Preprocessing Data
|
@else
|
||||||
</h3>
|
<a href="{{ $preprocessing->previousPageUrl() }}">‹</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
@php
|
||||||
<table class="w-full text-sm text-left">
|
$start = max($preprocessing->currentPage() - 2, 1);
|
||||||
<thead class="sticky top-0 bg-gray-100">
|
$end = min($preprocessing->currentPage() + 2, $preprocessing->lastPage());
|
||||||
<tr class="bg-gray-100 text-gray-600">
|
@endphp
|
||||||
<th class="px-4 py-3">Destinasi</th>
|
|
||||||
<th class="px-4 py-3">Ulasan Mentah</th>
|
|
||||||
<th class="px-4 py-3">Ulasan Bersih</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
{{-- NUMBER DINAMIS --}}
|
||||||
@forelse($preprocessing as $row)
|
@for ($i = $start; $i <= $end; $i++)
|
||||||
<tr class="border-b">
|
@if ($i == $preprocessing->currentPage())
|
||||||
|
<span class="px-3 py-1 bg-blue-600 text-white rounded-full">{{ $i }}</span>
|
||||||
|
@else
|
||||||
|
<a href="{{ $preprocessing->url($i) }}" class="px-3 py-1 hover:underline">
|
||||||
|
{{ $i }}
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
@endfor
|
||||||
|
|
||||||
<td class="px-4 py-3">
|
{{-- NEXT --}}
|
||||||
{{ $row->destinasi }}
|
@if ($preprocessing->hasMorePages())
|
||||||
</td>
|
<a href="{{ $preprocessing->nextPageUrl() }}">›</a>
|
||||||
|
@else
|
||||||
|
<span class="text-gray-400">›</span>
|
||||||
|
@endif
|
||||||
|
|
||||||
<td class="px-4 py-3">
|
{{-- LAST --}}
|
||||||
{{ $row->ulasan_asli }}
|
@if ($preprocessing->currentPage() < $preprocessing->lastPage())
|
||||||
</td>
|
<a href="{{ $preprocessing->url($preprocessing->lastPage()) }}">»</a>
|
||||||
|
@endif
|
||||||
|
|
||||||
<td class="px-4 py-3">
|
</div>
|
||||||
{{ $row->stemming }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
</tr>
|
</div>
|
||||||
|
<script>
|
||||||
@empty
|
document.getElementById("filterWisata").addEventListener("change", function () {
|
||||||
<tr>
|
let wisata = this.value;
|
||||||
|
|
||||||
<td colspan="3" class="text-center py-6 text-gray-400">
|
|
||||||
Belum ada hasil preprocessing.
|
|
||||||
</td>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
@endforelse
|
|
||||||
</tbody>
|
|
||||||
|
|
||||||
|
fetch(`/ulasan?wisata=${wisata}`)
|
||||||
|
.then(res => res.text())
|
||||||
|
.then(html => {
|
||||||
|
document.querySelector("#tableContainer").innerHTML =
|
||||||
|
new DOMParser().parseFromString(html, "text/html")
|
||||||
|
.querySelector("#tableContainer").innerHTML;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
import mysql.connector
|
|
||||||
import re
|
|
||||||
from Sastrawi.Stemmer.StemmerFactory import StemmerFactory
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# DATABASE
|
|
||||||
# =============================
|
|
||||||
conn = mysql.connector.connect(
|
|
||||||
host="localhost",
|
|
||||||
database="sentara",
|
|
||||||
user="root",
|
|
||||||
password=""
|
|
||||||
)
|
|
||||||
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# STEMMER
|
|
||||||
# =============================
|
|
||||||
factory = StemmerFactory()
|
|
||||||
stemmer = factory.create_stemmer()
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# AMBIL DATA ULASAN
|
|
||||||
# =============================
|
|
||||||
cursor.execute("SELECT id, ulasan FROM ulasan")
|
|
||||||
|
|
||||||
data = cursor.fetchall()
|
|
||||||
|
|
||||||
print("Total ulasan:", len(data))
|
|
||||||
|
|
||||||
for row in data:
|
|
||||||
|
|
||||||
id_ulasan = row[0]
|
|
||||||
text = row[1]
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# CLEANING
|
|
||||||
# =============================
|
|
||||||
text_clean = re.sub(r'[^a-zA-Z\s]', '', text)
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# CASE FOLDING
|
|
||||||
# =============================
|
|
||||||
text_clean = text_clean.lower()
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# STEMMING
|
|
||||||
# =============================
|
|
||||||
text_stem = stemmer.stem(text_clean)
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# INSERT KE DATABASE
|
|
||||||
# =============================
|
|
||||||
cursor.execute("""
|
|
||||||
INSERT INTO preprocessing_data
|
|
||||||
(ulasan_asli, cleaning, stemming)
|
|
||||||
VALUES (%s,%s,%s)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
text,
|
|
||||||
text_clean,
|
|
||||||
text_stem
|
|
||||||
))
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
print("Preprocessing selesai")
|
|
||||||
|
|
||||||
conn.close()
|
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
import mysql.connector
|
||||||
|
import re
|
||||||
|
import math
|
||||||
|
from collections import Counter
|
||||||
|
from Sastrawi.Stemmer.StemmerFactory import StemmerFactory
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# DATABASE
|
||||||
|
# =============================
|
||||||
|
conn = mysql.connector.connect(
|
||||||
|
host="localhost",
|
||||||
|
database="sentara",
|
||||||
|
user="root",
|
||||||
|
password=""
|
||||||
|
)
|
||||||
|
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# STEMMER
|
||||||
|
# =============================
|
||||||
|
factory = StemmerFactory()
|
||||||
|
stemmer = factory.create_stemmer()
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# DATA TRAINING
|
||||||
|
# =============================
|
||||||
|
data_train = [
|
||||||
|
("bagus bersih indah nyaman sejuk", "positif"),
|
||||||
|
("indah nyaman segar bagus", "positif"),
|
||||||
|
("kotor jelek rusak mahal sempit", "negatif"),
|
||||||
|
("jalan rusak sempit mahal", "negatif")
|
||||||
|
]
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# TRAIN NAIVE BAYES
|
||||||
|
# =============================
|
||||||
|
def train_nb(data):
|
||||||
|
word_counts = {"positif": Counter(), "negatif": Counter()}
|
||||||
|
class_counts = {"positif": 0, "negatif": 0}
|
||||||
|
total_words = {"positif": 0, "negatif": 0}
|
||||||
|
|
||||||
|
for text, label in data:
|
||||||
|
words = text.split()
|
||||||
|
class_counts[label] += 1
|
||||||
|
word_counts[label].update(words)
|
||||||
|
total_words[label] += len(words)
|
||||||
|
|
||||||
|
return word_counts, class_counts, total_words
|
||||||
|
|
||||||
|
word_counts, class_counts, total_words = train_nb(data_train)
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# PREDICT
|
||||||
|
# =============================
|
||||||
|
def predict_nb(text):
|
||||||
|
words = text.split()
|
||||||
|
|
||||||
|
vocab = set(word_counts["positif"]).union(set(word_counts["negatif"]))
|
||||||
|
|
||||||
|
log_pos = math.log(class_counts["positif"] / sum(class_counts.values()))
|
||||||
|
log_neg = math.log(class_counts["negatif"] / sum(class_counts.values()))
|
||||||
|
|
||||||
|
for word in words:
|
||||||
|
prob_pos = (word_counts["positif"][word] + 1) / (total_words["positif"] + len(vocab))
|
||||||
|
prob_neg = (word_counts["negatif"][word] + 1) / (total_words["negatif"] + len(vocab))
|
||||||
|
|
||||||
|
log_pos += math.log(prob_pos)
|
||||||
|
log_neg += math.log(prob_neg)
|
||||||
|
|
||||||
|
if log_pos > log_neg:
|
||||||
|
return "Positif", log_pos
|
||||||
|
else:
|
||||||
|
return "Negatif", log_neg
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# HAPUS DATA LAMA (PENTING!)
|
||||||
|
# =============================
|
||||||
|
cursor.execute("DELETE FROM preprocessing_data")
|
||||||
|
cursor.execute("DELETE FROM hasil_analisis")
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# AMBIL DATA
|
||||||
|
# =============================
|
||||||
|
cursor.execute("SELECT wisata, ulasan FROM ulasan")
|
||||||
|
data = cursor.fetchall()
|
||||||
|
|
||||||
|
print("Total ulasan:", len(data))
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# PROSES
|
||||||
|
# =============================
|
||||||
|
for row in data:
|
||||||
|
|
||||||
|
wisata = row[0]
|
||||||
|
text = row[1]
|
||||||
|
|
||||||
|
if not text:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# CLEANING
|
||||||
|
text_clean = re.sub(r'[^a-zA-Z\s]', '', text)
|
||||||
|
text_clean = text_clean.lower()
|
||||||
|
|
||||||
|
# TOKENIZING
|
||||||
|
tokens = text_clean.split()
|
||||||
|
text_token = ' '.join(tokens)
|
||||||
|
|
||||||
|
# STEMMING
|
||||||
|
text_stem = stemmer.stem(text_clean)
|
||||||
|
|
||||||
|
# PREDIKSI
|
||||||
|
sentimen, probabilitas = predict_nb(text_stem)
|
||||||
|
|
||||||
|
# INSERT PREPROCESSING
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO preprocessing_data
|
||||||
|
(wisata, ulasan_asli, cleaning, tokenizing, stemming)
|
||||||
|
VALUES (%s,%s,%s,%s,%s)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
wisata,
|
||||||
|
text,
|
||||||
|
text_clean,
|
||||||
|
text_token,
|
||||||
|
text_stem
|
||||||
|
))
|
||||||
|
|
||||||
|
# INSERT HASIL ANALISIS
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO hasil_analisis
|
||||||
|
(wisata, ulasan_terolah, hasil_preprocessing, sentimen, probabilitas)
|
||||||
|
VALUES (%s,%s,%s,%s,%s)
|
||||||
|
""", (wisata, text, text_stem, sentimen, probabilitas))
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# COMMIT SEKALI SAJA
|
||||||
|
# =============================
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
print(" Preprocessing & Analisis Sentimen selesai")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
|
@ -1,214 +1,143 @@
|
||||||
from selenium import webdriver
|
from selenium import webdriver
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.chrome.options import Options
|
from selenium.webdriver.chrome.options import Options
|
||||||
from selenium.webdriver.support.ui import WebDriverWait
|
|
||||||
from selenium.webdriver.support import expected_conditions as EC
|
|
||||||
import mysql.connector
|
import mysql.connector
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
# =============================
|
|
||||||
# DATABASE
|
# DATABASE
|
||||||
# =============================
|
|
||||||
conn = mysql.connector.connect(
|
conn = mysql.connector.connect(
|
||||||
host="localhost",
|
host="localhost",
|
||||||
database="sentara",
|
database="sentara",
|
||||||
user="root",
|
user="root",
|
||||||
password=""
|
password=""
|
||||||
)
|
)
|
||||||
|
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
# =============================
|
|
||||||
# CHROME
|
# CHROME
|
||||||
# =============================
|
|
||||||
options = Options()
|
options = Options()
|
||||||
options.add_argument("--start-maximized")
|
options.add_argument("--start-maximized")
|
||||||
|
|
||||||
driver = webdriver.Chrome(options=options)
|
driver = webdriver.Chrome(options=options)
|
||||||
wait = WebDriverWait(driver, 15)
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# DESTINASI
|
|
||||||
# =============================
|
|
||||||
destinations = {
|
destinations = {
|
||||||
"Pantai Papuma": "Pantai Papuma Jember",
|
"Pantai Papuma":"https://www.google.com/maps/place/Pantai+Papuma+Jember",
|
||||||
"Pantai Watu Ulo": "Pantai Watu Ulo Jember",
|
"Pantai Watu Ulo":"https://www.google.com/maps/place/Pantai+Watu+Ulo+Jember",
|
||||||
"Teluk Love": "Teluk Love Jember",
|
"Teluk Love":"https://www.google.com/maps/place/Teluk+Love+Jember",
|
||||||
"Kebun Teh Gunung Gambir": "Kebun Teh Gunung Gambir Jember"
|
"Kebun Teh Gunung Gambir":"https://www.google.com/maps/place/Kebun+Teh+Gunung+Gambir"
|
||||||
}
|
}
|
||||||
|
|
||||||
# =============================
|
for wisata, url in destinations.items():
|
||||||
# LOOP DESTINASI
|
|
||||||
# =============================
|
|
||||||
for nama_wisata, keyword in destinations.items():
|
|
||||||
|
|
||||||
saved = 0
|
print("\nScraping:", wisata)
|
||||||
print("\n====================")
|
|
||||||
print("Scraping:", nama_wisata)
|
|
||||||
print("====================")
|
|
||||||
|
|
||||||
driver.get("https://www.google.com/maps")
|
driver.get(url)
|
||||||
time.sleep(5)
|
time.sleep(10)
|
||||||
|
|
||||||
# =============================
|
# =========================
|
||||||
# CARI TEMPAT
|
# BUKA ULASAN (FIX)
|
||||||
# =============================
|
# =========================
|
||||||
try:
|
try:
|
||||||
search = driver.find_element(By.ID, "searchboxinput")
|
buttons = driver.find_elements(By.XPATH, "//button")
|
||||||
except:
|
|
||||||
search = driver.find_element(By.XPATH, "//input[@aria-label='Search Google Maps']")
|
|
||||||
|
|
||||||
search.clear()
|
ketemu = False
|
||||||
search.send_keys(keyword)
|
|
||||||
|
|
||||||
driver.find_element(By.ID, "searchbox-searchbutton").click()
|
for btn in buttons:
|
||||||
|
try:
|
||||||
|
label = btn.get_attribute("aria-label")
|
||||||
|
|
||||||
time.sleep(6)
|
if label and ("ulasan" in label.lower() or "review" in label.lower()):
|
||||||
|
driver.execute_script("arguments[0].click();", btn)
|
||||||
|
time.sleep(5)
|
||||||
|
print("Berhasil buka ulasan")
|
||||||
|
ketemu = True
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
# =============================
|
if not ketemu:
|
||||||
# KLIK HASIL PERTAMA
|
print("Tombol ulasan tidak ditemukan")
|
||||||
# =============================
|
continue
|
||||||
try:
|
|
||||||
first_result = wait.until(
|
|
||||||
EC.element_to_be_clickable((By.XPATH, "(//a[contains(@href,'/maps/place')])[1]"))
|
|
||||||
)
|
|
||||||
first_result.click()
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
except:
|
except Exception as e:
|
||||||
print("Tempat tidak ditemukan")
|
print("Error buka ulasan:", e)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# =============================
|
# =========================
|
||||||
# BUKA TAB REVIEW
|
# SCROLL
|
||||||
# =============================
|
# =========================
|
||||||
try:
|
|
||||||
review_button = wait.until(
|
|
||||||
EC.element_to_be_clickable((By.XPATH, "//button[contains(@aria-label,'review')]"))
|
|
||||||
)
|
|
||||||
|
|
||||||
review_button.click()
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
except:
|
|
||||||
print("Review button tidak ditemukan")
|
|
||||||
continue
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# SCROLL REVIEW
|
|
||||||
# =============================
|
|
||||||
try:
|
try:
|
||||||
scrollable_div = driver.find_element(By.XPATH, "//div[@role='region']")
|
scrollable_div = driver.find_element(By.XPATH, "//div[@role='region']")
|
||||||
except:
|
except:
|
||||||
print("Container review tidak ditemukan")
|
print("Container tidak ditemukan")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
last_count = 0
|
last_height = 0
|
||||||
same_count = 0
|
|
||||||
reviews = []
|
|
||||||
|
|
||||||
for i in range(200):
|
|
||||||
|
|
||||||
|
for i in range(50):
|
||||||
driver.execute_script(
|
driver.execute_script(
|
||||||
"arguments[0].scrollTop = arguments[0].scrollHeight",
|
"arguments[0].scrollTop = arguments[0].scrollHeight",
|
||||||
scrollable_div
|
scrollable_div
|
||||||
)
|
)
|
||||||
|
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
reviews = driver.find_elements(By.XPATH, "//div[@data-review-id]")
|
new_height = driver.execute_script(
|
||||||
|
"return arguments[0].scrollHeight",
|
||||||
|
scrollable_div
|
||||||
|
)
|
||||||
|
|
||||||
current_count = len(reviews)
|
print("Scroll ke-", i)
|
||||||
|
|
||||||
print("Scroll", i, "Review:", current_count)
|
if new_height == last_height:
|
||||||
|
print("Sudah mentok")
|
||||||
if current_count == last_count:
|
|
||||||
same_count += 1
|
|
||||||
else:
|
|
||||||
same_count = 0
|
|
||||||
|
|
||||||
if same_count >= 6:
|
|
||||||
print("✔ Semua review sudah termuat")
|
|
||||||
break
|
break
|
||||||
|
|
||||||
last_count = current_count
|
last_height = new_height
|
||||||
|
|
||||||
# =============================
|
|
||||||
# AMBIL DATA REVIEW
|
|
||||||
# =============================
|
|
||||||
for review in reviews:
|
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# EXPAND
|
||||||
|
# =========================
|
||||||
|
buttons = driver.find_elements(By.XPATH, "//button[contains(text(),'Selengkapnya')]")
|
||||||
|
for b in buttons:
|
||||||
try:
|
try:
|
||||||
|
driver.execute_script("arguments[0].click();", b)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
review_id = review.get_attribute("data-review-id")
|
time.sleep(2)
|
||||||
|
|
||||||
if not review_id:
|
# =========================
|
||||||
|
# AMBIL DATA
|
||||||
|
# =========================
|
||||||
|
reviews = driver.find_elements(By.XPATH, "//div[@data-review-id]")
|
||||||
|
|
||||||
|
print("Total review ditemukan:", len(reviews))
|
||||||
|
|
||||||
|
saved = 0
|
||||||
|
|
||||||
|
for review in reviews:
|
||||||
|
try:
|
||||||
|
reviewer = review.find_element(By.XPATH, ".//div[contains(@class,'d4r55')]").text
|
||||||
|
|
||||||
|
rating = review.find_element(By.XPATH, ".//span[@role='img']").get_attribute("aria-label")
|
||||||
|
|
||||||
|
ulasan = review.find_element(By.XPATH, ".//span[contains(@class,'wiI7pd')]").text
|
||||||
|
|
||||||
|
tanggal = review.find_element(By.XPATH, ".//span[contains(@class,'rsqaWe')]").text
|
||||||
|
|
||||||
|
if ulasan.strip() == "":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# cek duplikasi
|
|
||||||
cursor.execute(
|
|
||||||
"SELECT COUNT(*) FROM ulasan WHERE review_id=%s",
|
|
||||||
(review_id,)
|
|
||||||
)
|
|
||||||
|
|
||||||
exists = cursor.fetchone()[0]
|
|
||||||
|
|
||||||
if exists > 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# reviewer
|
|
||||||
try:
|
|
||||||
reviewer = review.find_element(
|
|
||||||
By.XPATH, ".//div[contains(@class,'d4r55')]"
|
|
||||||
).text
|
|
||||||
except:
|
|
||||||
reviewer = "Unknown"
|
|
||||||
|
|
||||||
# rating
|
|
||||||
try:
|
|
||||||
rating_text = review.find_element(
|
|
||||||
By.XPATH, ".//span[@role='img']"
|
|
||||||
).get_attribute("aria-label")
|
|
||||||
|
|
||||||
rating = float(rating_text.split()[0])
|
|
||||||
except:
|
|
||||||
rating = 0
|
|
||||||
|
|
||||||
# ulasan
|
|
||||||
try:
|
|
||||||
review_text = review.find_element(
|
|
||||||
By.XPATH, ".//span[contains(@class,'wiI7pd')]"
|
|
||||||
).text
|
|
||||||
except:
|
|
||||||
review_text = ""
|
|
||||||
|
|
||||||
# tanggal
|
|
||||||
try:
|
|
||||||
review_date = review.find_element(
|
|
||||||
By.XPATH, ".//span[contains(@class,'rsqaWe')]"
|
|
||||||
).text
|
|
||||||
except:
|
|
||||||
review_date = ""
|
|
||||||
|
|
||||||
if review_text == "":
|
|
||||||
continue
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
# INSERT DATABASE
|
|
||||||
# =============================
|
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
INSERT INTO ulasan
|
INSERT INTO ulasan
|
||||||
(review_id,destinasi,reviewer,ulasan,rating,tanggal,scraping_date)
|
(destinasi, reviewer, ulasan, rating, tanggal, scraping_date)
|
||||||
VALUES(%s,%s,%s,%s,%s,%s,%s)
|
VALUES (%s,%s,%s,%s,%s,%s)
|
||||||
""",
|
""", (
|
||||||
(
|
wisata,
|
||||||
review_id,
|
|
||||||
nama_wisata,
|
|
||||||
reviewer,
|
reviewer,
|
||||||
review_text,
|
ulasan,
|
||||||
rating,
|
rating,
|
||||||
review_date,
|
tanggal,
|
||||||
datetime.now()
|
datetime.now()
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
@ -218,11 +147,9 @@ for nama_wisata, keyword in destinations.items():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("skip:", e)
|
print("skip:", e)
|
||||||
|
|
||||||
print("Total tersimpan:", saved)
|
print("Berhasil simpan:", saved)
|
||||||
|
|
||||||
print("\n====================")
|
|
||||||
print("SCRAPING SELESAI")
|
|
||||||
print("====================")
|
|
||||||
|
|
||||||
driver.quit()
|
driver.quit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
print("\nSCRAPING SELESAI")
|
||||||
Loading…
Reference in New Issue