MIF_E31231226/resources/views/dashboard.blade.php

253 lines
11 KiB
PHP

@extends('layouts.sentara')
@section('content')
<div class="max-w-7xl mx-auto">
{{-- ALERT --}}
@if(!$hasAnalisis)
<div class="bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4 mb-6 rounded">
Data ulasan sudah tersedia, tetapi belum dilakukan analisis sentimen.
</div>
@endif
{{-- DROPDOWN PERIODE --}}
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-3">
<span class="text-sm text-gray-600 font-medium">Periode Analisis:</span>
<form method="GET" action="{{ route('dashboard') }}" id="periode-form">
<input type="hidden" name="tab" id="tab-hidden" value="{{ request('tab', 'dashboard') }}">
<input type="hidden" name="wisata" value="{{ request('wisata') }}">
<input type="hidden" name="destinasi" value="{{ request('destinasi') }}">
<select name="periode_id" onchange="document.getElementById('periode-form').submit()"
class="border rounded-lg px-3 py-2 text-sm bg-white shadow-sm">
@foreach($periodeList as $p)
<option value="{{ $p->id }}" {{ ($periodeAktif && $periodeAktif->id == $p->id) ? 'selected' : '' }}>
{{ $p->nama }}
</option>
@endforeach
</select>
</form>
</div>
@if($periodeAktif)
<span class="text-xs text-gray-400">
Data dianalisis: {{ \Carbon\Carbon::parse($periodeAktif->created_at)->format('d M Y') }}
</span>
@endif
</div>
<div x-data="{ tab: new URLSearchParams(window.location.search).get('tab') || 'dashboard' }" class="mb-6">
<!-- TAB -->
<div class="bg-blue-700 rounded-xl p-1 flex space-x-2 shadow mb-6">
<button @click="tab='dashboard'; document.getElementById('tab-hidden').value='dashboard'"
:class="tab==='dashboard' ? 'bg-white text-blue-700 shadow' : 'text-white hover:bg-blue-600'"
class="flex-1 py-3 rounded-lg text-sm font-medium transition">
Dashboard Sentara
</button>
<button @click="tab='analisis'; document.getElementById('tab-hidden').value='analisis'"
:class="tab==='analisis' ? 'bg-white text-blue-700 shadow' : 'text-white hover:bg-blue-600'"
class="flex-1 py-3 rounded-lg text-sm font-medium transition">
Hasil Analisis Sentimen
</button>
<button @click="tab='rekomendasi'; document.getElementById('tab-hidden').value='rekomendasi'"
:class="tab==='rekomendasi' ? 'bg-white text-blue-700 shadow' : 'text-white hover:bg-blue-600'"
class="flex-1 py-3 rounded-lg text-sm font-medium transition">
Rekomendasi Layanan
</button>
</div>
<!-- ================= DASHBOARD ================= -->
<div x-show="tab === 'dashboard'">
{{-- Statistik Cards --}}
<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">
<h3 class="font-semibold mb-4 text-gray-700">Distribusi Sentimen</h3>
<div style="height:300px;">
<canvas id="pieChart"></canvas>
</div>
</div>
{{-- BAR CHART - PURE HTML --}}
{{-- BAR CHART - PURE HTML VERTICAL --}}
<div class="bg-white rounded-xl shadow p-6">
<h3 class="font-semibold mb-4 text-gray-700">Sentimen per Destinasi</h3>
{{-- Legend --}}
<div class="flex gap-4 mb-4 text-xs">
<span class="flex items-center gap-1"><span class="w-3 h-3 rounded bg-blue-500 inline-block"></span> Positif</span>
<span class="flex items-center gap-1"><span class="w-3 h-3 rounded bg-red-400 inline-block"></span> Negatif</span>
<span class="flex items-center gap-1"><span class="w-3 h-3 rounded bg-yellow-400 inline-block"></span> Netral</span>
</div>
@php
$maxVal = 1;
foreach($chartDestinasi as $item) {
$maxVal = max($maxVal, $item->positif, $item->negatif, $item->netral);
}
@endphp
<div class="flex items-end gap-4 overflow-x-auto" style="height:220px;">
@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
<div class="flex flex-col items-center flex-1 min-w-[60px]">
<div class="flex items-end gap-1 w-full justify-center" style="height:180px;">
{{-- Positif --}}
<div class="relative group flex-1" style="height:{{ $hPos }}px; background:#3B82F6; border-radius:4px 4px 0 0; min-width:10px;">
<span class="absolute -top-5 left-1/2 -translate-x-1/2 text-xs text-blue-600 font-semibold">{{ $item->positif }}</span>
</div>
{{-- Negatif --}}
<div class="relative group flex-1" style="height:{{ $hNeg }}px; background:#F87171; border-radius:4px 4px 0 0; min-width:10px;">
<span class="absolute -top-5 left-1/2 -translate-x-1/2 text-xs text-red-500 font-semibold">{{ $item->negatif }}</span>
</div>
{{-- Netral --}}
<div class="relative group flex-1" style="height:{{ $hNet }}px; background:#FBBF24; border-radius:4px 4px 0 0; min-width:10px;">
<span class="absolute -top-5 left-1/2 -translate-x-1/2 text-xs text-yellow-500 font-semibold">{{ $item->netral }}</span>
</div>
</div>
<p class="text-xs text-gray-500 text-center mt-2 leading-tight" style="max-width:70px; word-break:break-word;">{{ $label }}</p>
</div>
@endforeach
</div>
</div>
</div>
{{-- Section Bawah --}}
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<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">
@foreach($totalPerWisata as $item)
<div class="flex justify-between border-b pb-3">
<span>{{ $item->wisata }}</span>
<span class="font-semibold text-gray-800">{{ $item->total }} ulasan</span>
</div>
@endforeach
</div>
</div>
<div class="bg-white rounded-xl shadow p-6 h-[320px]">
<h3 class="font-semibold mb-6 text-gray-700">Word Cloud</h3>
<div id="wordCloud" class="w-full h-[240px] overflow-hidden"></div>
</div>
</div>
</div>
<!-- ================= ANALISIS ================= -->
<div x-show="tab === 'analisis'" id="analisis-content">
@include('analisis.index')
</div>
<!-- ================= REKOMENDASI ================= -->
<div x-show="tab === 'rekomendasi'" id="rekomendasi-content">
@include('rekomendasi.index')
</div>
</div>
</div>
{{-- SCRIPTS --}}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/wordcloud2.js/1.1.2/wordcloud2.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
// PIE CHART
const pieData = JSON.parse('{!! json_encode($chartSentimen) !!}');
const pieCanvas = document.getElementById('pieChart');
if (window.pieChartInstance) window.pieChartInstance.destroy();
window.pieChartInstance = new Chart(pieCanvas, {
type: 'pie',
data: {
labels: pieData.map(i => i.sentimen),
datasets: [{
data: pieData.map(i => i.total),
backgroundColor: ['#3B82F6', '#EF4444', '#F59E0B']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false
}
});
// WORD CLOUD
const text = `{!! addslashes($allText) !!}`;
if (text.length > 0) {
const wordCount = {};
text.split(" ").forEach(word => {
if (word.length > 3) wordCount[word] = (wordCount[word] || 0) + 1;
});
WordCloud(document.getElementById('wordCloud'), { list: Object.entries(wordCount) });
}
});
</script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const container = document.getElementById('analisis-content');
// FILTER AJAX
document.addEventListener('submit', function(e){
if(e.target.id === 'filter-form'){
e.preventDefault();
const params = new URLSearchParams(new FormData(e.target)).toString();
fetch('/dashboard?' + params)
.then(res => res.text())
.then(html => {
const doc = new DOMParser().parseFromString(html, 'text/html');
container.innerHTML = doc.getElementById('analisis-content').innerHTML;
});
}
});
// PAGINATION AJAX
document.addEventListener('click', function(e){
const link = e.target.closest('a');
if(link && link.href.includes('page=')){
e.preventDefault();
fetch(link.href)
.then(res => res.text())
.then(html => {
const doc = new DOMParser().parseFromString(html, 'text/html');
container.innerHTML = doc.getElementById('analisis-content').innerHTML;
});
}
});
});
</script>
@endsection