laporan tahunan

This commit is contained in:
MufridaFaraDiani27 2026-05-29 13:23:40 +07:00
parent 80057c1acc
commit 6f6eabcaef
10 changed files with 1939 additions and 1304 deletions

View File

@ -19,95 +19,432 @@ public function downloadBulanan($periode)
$data = $this->collectData($periode); $data = $this->collectData($periode);
$data['periode'] = $periodeData; $data['periode'] = $periodeData;
$data['judulLaporan'] = 'Laporan Analisis Sentimen - ' . ($periodeData->nama ?? '-');
$data['evaluasi'] = DB::table('evaluasi_model')->orderByDesc('id')->first()
?? (object)['accuracy' => 0];
// Ulasan KOTOR — dari tabel ulasan mentah $data['judulLaporan'] =
$data['totalUlasanKotor'] = DB::table('ulasan') 'Laporan Analisis Sentimen - ' .
($periodeData->nama ?? '-');
$data['evaluasi'] =
DB::table('evaluasi_model')
->orderByDesc('id')
->first()
?? (object)['accuracy' => 0];
// TOTAL ULASAN KOTOR
$data['totalUlasanKotor'] =
DB::table('ulasan')
->where('periode_id', $periode) ->where('periode_id', $periode)
->count(); ->count();
// Trend by tanggal ULASAN (bukan created_at analisis) // TREND HARIAN
$data['trendHarian'] = DB::table('hasil_analisis') $data['trendHarian'] =
DB::table('hasil_analisis')
->select( ->select(
DB::raw('DATE(created_at) as tanggal'), // ✅ ganti tanggal → created_at DB::raw('DATE(created_at) as tanggal'),
DB::raw("SUM(CASE WHEN sentimen='positif' THEN 1 ELSE 0 END) as positif"),
DB::raw("SUM(CASE WHEN sentimen='netral' THEN 1 ELSE 0 END) as netral"), DB::raw("
DB::raw("SUM(CASE WHEN sentimen='negatif' THEN 1 ELSE 0 END) as negatif") SUM(
CASE
WHEN sentimen='positif'
THEN 1 ELSE 0 END
) as positif
"),
DB::raw("
SUM(
CASE
WHEN sentimen='netral'
THEN 1 ELSE 0 END
) as netral
"),
DB::raw("
SUM(
CASE
WHEN sentimen='negatif'
THEN 1 ELSE 0 END
) as negatif
")
) )
->where('periode_id', $periode) ->where('periode_id', $periode)
->groupBy(DB::raw('DATE(created_at)')) // ✅ ganti tanggal → created_at ->groupBy(DB::raw('DATE(created_at)'))
->orderBy(DB::raw('DATE(created_at)')) // ✅ ganti tanggal → created_at ->orderBy(DB::raw('DATE(created_at)'))
->get(); ->get();
return view('laporan.bulanan', $data); return view(
'laporan.bulanan',
$data
);
} }
public function downloadTahunan(Request $request) public function downloadTahunan(Request $request)
{ {
$tahun = $request->tahun; $tahun = $request->tahun;
$periodeList = DB::table('periode_analisis') $periodeList =
DB::table('periode_analisis')
->where('tahun', $tahun) ->where('tahun', $tahun)
->orderBy('bulan') ->orderBy('bulan')
->get(); ->get();
if ($periodeList->isEmpty()) { if ($periodeList->isEmpty()) {
return back()->with('error', 'Tidak ada data tahun tersebut');
return back()->with(
'error',
'Tidak ada data tahun tersebut'
);
} }
return view('laporan.tahunan', compact('tahun', 'periodeList')); $periodeIds =
$periodeList->pluck('id');
// TOTAL ULASAN
$totalUlasan =
DB::table('hasil_analisis')
->whereIn(
'periode_id',
$periodeIds
)
->count();
// TOTAL SENTIMEN
$positif =
DB::table('hasil_analisis')
->whereIn(
'periode_id',
$periodeIds
)
->where(
'sentimen',
'positif'
)
->count();
$netral =
DB::table('hasil_analisis')
->whereIn(
'periode_id',
$periodeIds
)
->where(
'sentimen',
'netral'
)
->count();
$negatif =
DB::table('hasil_analisis')
->whereIn(
'periode_id',
$periodeIds
)
->where(
'sentimen',
'negatif'
)
->count();
// PERSEN POSITIF
$persenPositif =
$totalUlasan > 0
? round(
($positif / $totalUlasan) * 100,
2
)
: 0;
// AKURASI
$akurasi =
DB::table('evaluasi_model')
->latest('id')
->value('accuracy')
?? 0;
// TOTAL WISATA
$totalWisata =
DB::table('hasil_analisis')
->whereIn(
'periode_id',
$periodeIds
)
->distinct()
->count('wisata');
// REKAP BULANAN
$rekapBulanan =
DB::table('hasil_analisis as h')
->join(
'periode_analisis as p',
'h.periode_id',
'=',
'p.id'
)
->select(
'p.nama as bulan',
DB::raw(
'COUNT(*) as total'
),
DB::raw("
SUM(
CASE
WHEN sentimen='positif'
THEN 1 ELSE 0 END
) as positif
"),
DB::raw("
SUM(
CASE
WHEN sentimen='netral'
THEN 1 ELSE 0 END
) as netral
"),
DB::raw("
SUM(
CASE
WHEN sentimen='negatif'
THEN 1 ELSE 0 END
) as negatif
")
)
->whereIn(
'periode_id',
$periodeIds
)
->groupBy(
'p.nama',
'p.bulan'
)
->orderBy(
'p.bulan'
)
->get();
// WISATA POPULER
$wisataPopuler =
DB::table('hasil_analisis')
->select(
'wisata',
DB::raw(
'COUNT(*) as jumlah'
),
DB::raw("
ROUND(
(
SUM(
CASE
WHEN sentimen='positif'
THEN 1 ELSE 0 END
)
/
COUNT(*)
) * 100
,2)
as persen
")
)
->whereIn(
'periode_id',
$periodeIds
)
->groupBy(
'wisata'
)
->orderByDesc(
'persen'
)
->limit(5)
->get();
return view(
'laporan.tahunan',
compact(
'tahun',
'periodeList',
'totalUlasan',
'akurasi',
'totalWisata',
'rekapBulanan',
'wisataPopuler',
'positif',
'netral',
'negatif',
'persenPositif'
)
);
} }
private function collectData($periodeId) private function collectData($periodeId)
{ {
$totalUlasan = DB::table('hasil_analisis') $totalUlasan =
->where('periode_id', $periodeId) DB::table('hasil_analisis')
->where(
'periode_id',
$periodeId
)
->count(); ->count();
$totalPositif = DB::table('hasil_analisis') $totalPositif =
->where('periode_id', $periodeId) DB::table('hasil_analisis')
->where('sentimen', 'positif') ->where(
'periode_id',
$periodeId
)
->where(
'sentimen',
'positif'
)
->count(); ->count();
$totalNetral = DB::table('hasil_analisis') $totalNetral =
->where('periode_id', $periodeId) DB::table('hasil_analisis')
->where('sentimen', 'netral') ->where(
'periode_id',
$periodeId
)
->where(
'sentimen',
'netral'
)
->count(); ->count();
$totalNegatif = DB::table('hasil_analisis') $totalNegatif =
->where('periode_id', $periodeId) DB::table('hasil_analisis')
->where('sentimen', 'negatif') ->where(
'periode_id',
$periodeId
)
->where(
'sentimen',
'negatif'
)
->count(); ->count();
$persen = [ $persen = [
'positif' => $totalUlasan ? round(($totalPositif / $totalUlasan) * 100, 2) : 0,
'netral' => $totalUlasan ? round(($totalNetral / $totalUlasan) * 100, 2) : 0, 'positif' =>
'negatif' => $totalUlasan ? round(($totalNegatif / $totalUlasan) * 100, 2) : 0, $totalUlasan
? round(
($totalPositif /
$totalUlasan) * 100,
2
)
: 0,
'netral' =>
$totalUlasan
? round(
($totalNetral /
$totalUlasan) * 100,
2
)
: 0,
'negatif' =>
$totalUlasan
? round(
($totalNegatif /
$totalUlasan) * 100,
2
)
: 0,
]; ];
$perWisata = DB::table('hasil_analisis') $perWisata =
DB::table('hasil_analisis')
->select( ->select(
'wisata', 'wisata',
DB::raw('COUNT(*) as total'),
DB::raw("SUM(CASE WHEN sentimen='positif' THEN 1 ELSE 0 END) as positif"), DB::raw(
DB::raw("SUM(CASE WHEN sentimen='netral' THEN 1 ELSE 0 END) as netral"), 'COUNT(*) as total'
DB::raw("SUM(CASE WHEN sentimen='negatif' THEN 1 ELSE 0 END) as negatif") ),
DB::raw("
SUM(
CASE
WHEN sentimen='positif'
THEN 1 ELSE 0 END
) as positif
"),
DB::raw("
SUM(
CASE
WHEN sentimen='netral'
THEN 1 ELSE 0 END
) as netral
"),
DB::raw("
SUM(
CASE
WHEN sentimen='negatif'
THEN 1 ELSE 0 END
) as negatif
")
) )
->where('periode_id', $periodeId)
->groupBy('wisata') ->where(
->orderByDesc('positif') 'periode_id',
$periodeId
)
->groupBy(
'wisata'
)
->orderByDesc(
'positif'
)
->get(); ->get();
return [ return [
'totalUlasan' => $totalUlasan,
'totalPositif' => $totalPositif, 'totalUlasan' =>
'totalNetral' => $totalNetral, $totalUlasan,
'totalNegatif' => $totalNegatif,
'persen' => $persen, 'totalPositif' =>
'perWisata' => $perWisata, $totalPositif,
'totalNetral' =>
$totalNetral,
'totalNegatif' =>
$totalNegatif,
'persen' =>
$persen,
'perWisata' =>
$perWisata,
]; ];
} }
} }

View File

@ -1,110 +1,605 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Laporan Tahunan</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
<title> body {
Laporan Tahunan font-family: Arial, sans-serif;
</title> background: #f3f5fb;
padding: 24px;
color: #1f2937;
}
<style> /* ACTION BAR */
.action-bar {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 960px;
margin: 0 auto 16px;
}
body{ .logo-text {
font-family: sans-serif; font-size: 22px;
font-size: 12px; font-weight: 900;
color: #222; color: #1e3a8a;
} letter-spacing: -1px;
}
.logo-text span { color: #ef4444; }
h1,h2,h3{ .btn-group { display: flex; gap: 8px; }
margin-bottom: 5px;
}
.title{ .btn {
text-align: center; display: inline-flex;
margin-bottom: 30px; align-items: center;
} gap: 6px;
padding: 9px 18px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
border: none;
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
.btn-blue { background: #1d4ed8; color: white; }
.btn-white { background: white; color: #374151; border: 1px solid #d1d5db; }
.btn:hover { opacity: .88; }
table{ /* CONTAINER */
width: 100%; .container {
border-collapse: collapse; max-width: 960px;
margin-top: 10px; margin: 0 auto;
} background: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0 4px 20px rgba(0,0,0,0.07);
}
table th{ /* HERO */
background: #f3f3f3; .hero {
} border: 1px solid #dbeafe;
background: linear-gradient(135deg, #eff6ff, #f7f8ff);
border-radius: 12px;
padding: 18px 20px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
table th, .hero-left { display: flex; gap: 16px; align-items: center; }
table td{
border: 1px solid #ccc;
padding: 8px;
}
</style> .hero-icon {
width: 52px;
height: 52px;
background: #1d4ed8;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.hero-icon svg { width: 28px; height: 28px; fill: white; }
.hero-title { font-size: 18px; font-weight: 900; color: #132b70; line-height: 1.3; }
.hero-sub { font-size: 11px; color: #6b7280; margin-top: 4px; }
.hero-right { font-size: 12px; text-align: right; line-height: 1.8; }
.hero-right .year { font-size: 22px; font-weight: 800; color: #1e3a8a; }
/* SECTION TITLE */
.section-title {
font-size: 12px;
font-weight: 700;
color: #1e3a8a;
text-transform: uppercase;
letter-spacing: .6px;
margin: 20px 0 10px;
display: flex;
align-items: center;
gap: 8px;
}
.section-title::before {
content: '';
display: inline-block;
width: 4px;
height: 14px;
background: #1d4ed8;
border-radius: 2px;
}
/* CARDS */
.cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-bottom: 20px;
}
.card {
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 14px 16px;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.card-info .card-label { font-size: 11px; color: #6b7280; margin-bottom: 4px; }
.card-info .card-value { font-size: 28px; font-weight: 800; line-height: 1; }
.card-info .card-unit { font-size: 11px; color: #9ca3af; margin-top: 4px; }
.card-icon {
width: 36px; height: 36px;
border-radius: 8px;
display: flex; align-items: center; justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.blue { background: #eff6ff; border-color: #bfdbfe; }
.green { background: #f0fdf4; border-color: #bbf7d0; }
.yellow { background: #fffbeb; border-color: #fde68a; }
.purple { background: #faf5ff; border-color: #e9d5ff; }
.blue .card-icon { background: #dbeafe; }
.green .card-icon { background: #dcfce7; }
.yellow .card-icon { background: #fef3c7; }
.purple .card-icon { background: #ede9fe; }
/* REKAP + DONUT ROW */
.grid2 {
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 16px;
margin-bottom: 16px;
}
/* TABLE */
.table-box {
border: 1px solid #e5e7eb;
border-radius: 12px;
overflow: hidden;
}
table { width: 100%; border-collapse: collapse; }
table thead tr { background: #f8fafc; }
table th { padding: 10px 12px; font-size: 11px; font-weight: 700; color: #374151; text-align: left; }
table td { padding: 9px 12px; font-size: 11px; border-top: 1px solid #f1f5f9; }
table tbody tr:hover { background: #fafafa; }
.tfoot-row td { background: #f8fafc; font-weight: 700; border-top: 2px solid #e5e7eb; }
.pos { color: #16a34a; font-weight: 600; }
.net { color: #d97706; font-weight: 600; }
.neg { color: #dc2626; font-weight: 600; }
.badge-green {
background: #dcfce7;
color: #15803d;
border-radius: 20px;
padding: 2px 8px;
font-weight: 700;
font-size: 11px;
}
/* DONUT BOX */
.donut-box {
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 16px;
display: flex;
flex-direction: column;
}
.donut-box h4 {
font-size: 11px;
font-weight: 700;
color: #1e3a8a;
text-transform: uppercase;
letter-spacing: .4px;
margin-bottom: 10px;
}
.donut-wrap {
position: relative;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.donut-center {
position: absolute;
text-align: center;
pointer-events: none;
}
.donut-center .dc-num { font-size: 20px; font-weight: 800; color: #1e3a8a; }
.donut-center .dc-label { font-size: 9px; color: #6b7280; }
/* TREND BOX */
.trend-box {
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 16px;
margin-bottom: 16px;
}
.trend-box h4 {
font-size: 11px;
font-weight: 700;
color: #1e3a8a;
text-transform: uppercase;
letter-spacing: .4px;
margin-bottom: 10px;
}
/* BOTTOM GRID */
.bottom-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 20px;
}
/* KESIMPULAN */
.kesimpulan-box {
border: 1px solid #dbeafe;
background: #eff6ff;
border-radius: 12px;
padding: 16px 18px;
font-size: 12px;
line-height: 1.7;
}
.kesimpulan-box h4 {
font-size: 12px;
font-weight: 700;
color: #1e3a8a;
margin-bottom: 8px;
text-transform: uppercase;
}
/* FOOTER */
.footer {
display: flex;
justify-content: space-between;
font-size: 11px;
color: #9ca3af;
padding-top: 14px;
border-top: 1px solid #f1f5f9;
}
@media print {
body { background: white; padding: 0; }
.action-bar { display: none !important; }
.container { box-shadow: none; }
}
</style>
</head> </head>
<body> <body>
<div class="title"> {{-- ACTION BAR --}}
<div class="action-bar no-print" id="actionBar">
<div></div>
<div class="btn-group">
<button class="btn btn-white" onclick="window.print()">🖨 Cetak</button>
</div>
</div>
<h1> <div class="container" id="isiLaporan">
LAPORAN TAHUNAN ANALISIS SENTIMEN
</h1>
<h2>
Wisata Kabupaten Jember
</h2>
<p>
Tahun:
<strong>
{{ $tahun }}
</strong>
</p>
{{-- HERO --}}
<div class="hero">
<div class="hero-left">
<div class="hero-icon">
<svg viewBox="0 0 24 24"><path d="M9 12h6m-6 4h6M7 4H5a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2V6a2 2 0 00-2-2h-2M9 4a2 2 0 002 2h2a2 2 0 002-2M9 4a2 2 0 012-2h2a2 2 0 012 2" stroke="white" stroke-width="2" stroke-linecap="round" fill="none"/></svg>
</div>
<div>
{{-- Logo kecil di atas judul --}}
<div style="display:inline-flex;align-items:center;gap:5px;background:#1e3a8a;border-radius:6px;padding:3px 10px;margin-bottom:7px;">
<span style="color:#fff;font-size:11px;font-weight:900;letter-spacing:1px;">-SENTARA-</span>
<span style="color:#93c5fd;font-size:9px;letter-spacing:2px;">JEMBER</span>
</div>
<div class="hero-title">LAPORAN ANALISIS SENTIMEN<br>PER TAHUN</div>
<div class="hero-sub">SISTEM ANALISIS SENTIMEN WISATA JEMBER</div>
</div>
</div>
<div class="hero-right">
<div style="color:#6b7280;font-size:11px;">Tahun</div>
<div class="year">{{ $tahun }}</div>
<div style="color:#6b7280;font-size:11px;margin-top:6px;">Tanggal Cetak</div>
<div style="font-weight:600;">{{ now()->format('d M Y H:i') }}</div>
</div>
</div> </div>
<table> {{-- RINGKASAN --}}
<div class="section-title">Ringkasan Tahunan</div>
<tr> <div class="cards">
<th>Total Ulasan</th> <div class="card blue">
<td>{{ $totalUlasan ?? 0 }}</td> <div class="card-info">
</tr> <div class="card-label">Total Ulasan</div>
<div class="card-value">{{ $totalUlasan }}</div>
<div class="card-unit">Ulasan</div>
</div>
<div class="card-icon">💬</div>
</div>
<div class="card green">
<div class="card-info">
<div class="card-label">Rata-rata Akurasi</div>
<div class="card-value" style="font-size:20px;">{{ $akurasi }}%</div>
<div class="card-unit">Akurasi</div>
</div>
<div class="card-icon"></div>
</div>
<div class="card yellow">
<div class="card-info">
<div class="card-label">Total Wisata Dibahas</div>
<div class="card-value">{{ $totalWisata }}</div>
<div class="card-unit">Wisata</div>
</div>
<div class="card-icon">📍</div>
</div>
<div class="card purple">
<div class="card-info">
<div class="card-label">Bulan Dianalisis</div>
<div class="card-value">{{ count($rekapBulanan) }}</div>
<div class="card-unit">Bulan</div>
</div>
<div class="card-icon">📅</div>
</div>
</div>
<tr> {{-- REKAP + DONUT --}}
<th>Positif</th> <div class="section-title">Rekap Sentimen per Bulan</div>
<td>{{ $positif ?? 0 }}</td>
</tr>
<tr> <div class="grid2">
<th>Negatif</th> <div class="table-box">
<td>{{ $negatif ?? 0 }}</td> <table>
</tr> <thead>
<tr>
<th>Bulan</th>
<th>Total Ulasan</th>
<th>Positif</th>
<th>Netral</th>
<th>Negatif</th>
<th>Persentase Positif</th>
</tr>
</thead>
<tbody>
@php $totP=0; $totN=0; $totNt=0; $totAll=0; @endphp
@foreach($rekapBulanan as $r)
@php $totP+=$r->positif; $totN+=$r->negatif; $totNt+=$r->netral; $totAll+=$r->total; @endphp
<tr>
<td>{{ $r->bulan }}</td>
<td>{{ $r->total }}</td>
<td class="pos">{{ $r->positif }}</td>
<td class="net">{{ $r->netral }}</td>
<td class="neg">{{ $r->negatif }}</td>
<td><span class="badge-green">{{ number_format(($r->positif/max($r->total,1))*100,2) }}%</span></td>
</tr>
@endforeach
</tbody>
<tfoot>
<tr class="tfoot-row">
<td>TOTAL / RATA-RATA</td>
<td>{{ $totAll }}</td>
<td class="pos">{{ $totP }}</td>
<td class="net">{{ $totNt }}</td>
<td class="neg">{{ $totN }}</td>
<td><span class="badge-green">{{ $totAll > 0 ? number_format(($totP/$totAll)*100,2) : 0 }}%</span></td>
</tr>
</tfoot>
</table>
</div>
<tr> <div class="donut-box">
<th>Netral</th> <h4>Distribusi Sentimen Tahun {{ $tahun }}</h4>
<td>{{ $netral ?? 0 }}</td> <div class="donut-wrap" style="height:220px;">
</tr> <canvas id="donut"></canvas>
</div>
</div>
</div>
</table> {{-- TREND --}}
<div class="section-title">Trend Sentimen per Bulan ({{ $tahun }})</div>
<br> <div class="trend-box">
<canvas id="trend" height="90"></canvas>
</div>
<h3>Rekomendasi Tahunan</h3> {{-- WISATA + KESIMPULAN --}}
<div class="section-title">Wisata Serpopuler & Kesimpulan</div>
@forelse(($rekomendasi ?? []) as $item) <div class="bottom-grid">
<div class="table-box">
<table>
<thead>
<tr>
<th>No</th>
<th>Wisata</th>
<th>Total Ulasan</th>
<th>Persentase Positif</th>
</tr>
</thead>
<tbody>
@foreach($wisataPopuler as $i => $w)
<tr>
<td>{{ $i+1 }}</td>
<td>{{ $w->wisata }}</td>
<td>{{ $w->jumlah }}</td>
<td><span class="badge-green">{{ $w->persen }}%</span></td>
</tr>
@endforeach
</tbody>
</table>
</div>
<p> <div class="kesimpulan-box">
{{ $item->rekomendasi }} <h4>Kesimpulan Tahunan</h4>
</p> <p>
Secara umum, sentimen positif mendominasi tahun <b>{{ $tahun }}</b>
dengan rata-rata <b>{{ $persenPositif }}%</b>.
@if(isset($wisataPopuler[0]))
{{ $wisataPopuler[0]->wisata }} menjadi destinasi paling populer
dengan sentimen positif tertinggi.
@endif
Diperlukan peningkatan pada aspek fasilitas, kebersihan, dan pelayanan
untuk menekan sentimen negatif.
</p>
</div>
</div>
@empty {{-- FOOTER --}}
<div class="footer">
<div>SENTARA - Sistem Analisis Sentimen Wisata Jember</div>
<div>Dicetak oleh: {{ auth()->user()->name }} ({{ ucfirst(auth()->user()->role ?? 'Admin') }})</div>
</div>
<p> </div>
Tidak ada rekomendasi.
</p>
@endforelse <script>
// ── DONUT ──
const donutCtx = document.getElementById('donut');
const totalUlasan = {{ $totalUlasan > 0 ? $totalUlasan : 1 }};
const donutData = [{{ $positif }}, {{ $netral }}, {{ $negatif }}];
// Plugin: angka total di tengah (tidak numpuk)
const centerTextPlugin = {
id: 'centerText',
beforeDraw(chart) {
const { ctx, chartArea: { left, top, right, bottom } } = chart;
const cx = (left + right) / 2;
const cy = (top + bottom) / 2;
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#1e3a8a';
ctx.font = 'bold 22px Arial';
ctx.fillText(totalUlasan, cx, cy - 8);
ctx.fillStyle = '#9ca3af';
ctx.font = '11px Arial';
ctx.fillText('Total Ulasan', cx, cy + 12);
ctx.restore();
}
};
new Chart(donutCtx, {
type: 'doughnut',
data: {
labels: ['Positif', 'Netral', 'Negatif'],
datasets: [{
data: donutData,
backgroundColor: ['#22c55e', '#f59e0b', '#ef4444'],
borderWidth: 3,
borderColor: '#fff',
hoverOffset: 6,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false,
cutout: '62%',
plugins: {
legend: {
position: 'bottom',
labels: {
font: { size: 11 },
padding: 10,
generateLabels(chart) {
const d = chart.data;
return d.labels.map((lbl, i) => {
const val = d.datasets[0].data[i];
const pct = Math.round(val / totalUlasan * 1000) / 10;
return {
text: `${lbl} ${val} (${pct}%)`,
fillStyle: d.datasets[0].backgroundColor[i],
strokeStyle: '#fff',
lineWidth: 2,
index: i,
};
});
}
}
},
tooltip: {
callbacks: {
label(ctx) {
const pct = Math.round(ctx.parsed / totalUlasan * 1000) / 10;
return ` ${ctx.label}: ${ctx.parsed} (${pct}%)`;
}
}
}
}
},
plugins: [centerTextPlugin]
});
// ── TREND LINE ──
new Chart(document.getElementById('trend'), {
type: 'line',
data: {
labels: [
@foreach($rekapBulanan as $r)
'{{ $r->bulan }}',
@endforeach
],
datasets: [
{
label: 'Positif',
data: [@foreach($rekapBulanan as $r){{ $r->positif }},@endforeach],
borderColor: '#22c55e',
backgroundColor: 'rgba(34,197,94,0.08)',
tension: 0.4, fill: true, pointRadius: 4, pointBackgroundColor: '#22c55e'
},
{
label: 'Netral',
data: [@foreach($rekapBulanan as $r){{ $r->netral }},@endforeach],
borderColor: '#f59e0b',
backgroundColor: 'rgba(245,158,11,0.05)',
tension: 0.4, fill: false, pointRadius: 4, pointBackgroundColor: '#f59e0b',
borderDash: [4,3]
},
{
label: 'Negatif',
data: [@foreach($rekapBulanan as $r){{ $r->negatif }},@endforeach],
borderColor: '#ef4444',
backgroundColor: 'rgba(239,68,68,0.05)',
tension: 0.4, fill: false, pointRadius: 4, pointBackgroundColor: '#ef4444'
}
]
},
options: {
responsive: true,
animation: false,
plugins: {
legend: { position: 'bottom', labels: { font: { size: 11 }, padding: 12 } }
},
scales: {
y: { beginAtZero: true, ticks: { font: { size: 10 } }, grid: { color: '#f1f5f9' } },
x: { ticks: { font: { size: 10 } }, grid: { display: false } }
}
}
});
// ── SIMPAN PDF ──
function simpanPDF() {
const el = document.getElementById('isiLaporan');
const bar = document.getElementById('actionBar');
bar.style.display = 'none';
html2pdf().set({
margin: 8,
filename: 'Laporan-Tahunan-{{ $tahun }}.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true, windowWidth: 960 },
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
}).from(el).save().then(() => {
bar.style.display = 'flex';
});
}
</script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,5 @@
<?php $__env->startSection('title', __('Not Found')); ?>
<?php $__env->startSection('code', '404'); ?>
<?php $__env->startSection('message', __('Not Found')); ?>
<?php echo $__env->make('errors::minimal', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH C:\laragon\www\SistemAnalisisSentimen\vendor\laravel\framework\src\Illuminate\Foundation\Exceptions/views/404.blade.php ENDPATH**/ ?>

View File

@ -1,377 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><?php echo e($judulLaporan); ?></title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<style>
* { box-sizing: border-box; }
body {
font-family: Arial, sans-serif;
background: #f1f5f9;
color: #1f2937;
margin: 0;
padding: 24px;
}
/* ── Sticky action bar pojok kanan atas ── */
.action-bar {
position: fixed;
top: 16px;
right: 24px;
display: flex;
gap: 8px;
z-index: 999;
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 9px 18px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
border: none;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
transition: opacity .2s;
}
.btn:hover { opacity: .85; }
.btn-primary { background: #1d4ed8; color: white; }
.btn-secondary { background: white; color: #374151; border: 1px solid #d1d5db; }
.container {
max-width: 960px;
margin: 0 auto;
background: white;
border-radius: 16px;
padding: 28px;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
}
/* LOGO */
.logo { font-size: 26px; font-weight: 800; color: #1e3a8a; margin-bottom: 20px; }
/* HERO */
.hero {
border: 1px solid #dbeafe;
background: linear-gradient(135deg, #eff6ff, #f7f8ff);
border-radius: 12px;
padding: 22px 24px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.hero-title { font-size: 22px; font-weight: 800; color: #132b70; line-height: 1.3; }
.hero-sub { margin-top: 6px; color: #374151; font-size: 13px; }
.hero-right {
min-width: 160px;
border: 1px solid #dbeafe;
border-radius: 10px;
padding: 14px 16px;
background: white;
font-size: 13px;
line-height: 1.8;
}
/* SECTION TITLE */
.section-title {
font-weight: 700;
font-size: 13px;
color: #1e3a8a;
letter-spacing: .5px;
margin: 24px 0 14px;
text-transform: uppercase;
}
/* CARDS */
.cards { display: flex; gap: 12px; margin-bottom: 24px; }
.card {
flex: 1;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 16px;
}
.card .card-label { font-size: 12px; color: #6b7280; margin-bottom: 6px; }
.card .card-value { font-size: 26px; font-weight: 800; margin: 0; }
.card .card-unit { font-size: 12px; color: #9ca3af; margin-top: 4px; }
.blue { background: #eff6ff; border-color: #bfdbfe; }
.green { background: #f0fdf4; border-color: #bbf7d0; }
.yellow { background: #fffbeb; border-color: #fde68a; }
.purple { background: #faf5ff; border-color: #e9d5ff; }
/* CHARTS */
.chart-row { display: flex; gap: 16px; margin-bottom: 24px; align-items: flex-start; }
.box-pie {
width: 260px;
flex-shrink: 0;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 16px;
display: flex;
flex-direction: column;
align-items: center;
}
.box-bar {
flex: 1;
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 16px;
min-width: 0;
}
.box-pie h4, .box-bar h4 {
margin: 0 0 12px;
font-size: 12px;
color: #1e3a8a;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .4px;
width: 100%;
}
/* TABLE */
table { width: 100%; border-collapse: collapse; margin-top: 8px; }
table th { background: #f8fafc; font-size: 12px; font-weight: 600; }
table th, table td { border: 1px solid #e5e7eb; padding: 9px 11px; font-size: 12px; }
.badge {
background: #dcfce7;
color: #15803d;
border-radius: 20px;
padding: 3px 10px;
font-weight: 600;
font-size: 11px;
}
/* KESIMPULAN */
.kesimpulan {
border: 1px solid #dbeafe;
background: #eff6ff;
border-radius: 10px;
padding: 16px 18px;
margin-top: 20px;
font-size: 13px;
}
/* FOOTER */
.footer {
margin-top: 24px;
padding-top: 14px;
border-top: 1px solid #e5e7eb;
font-size: 12px;
display: flex;
justify-content: space-between;
color: #6b7280;
}
/* Sembunyikan action bar saat print */
@media print {
body { background: white; padding: 0; }
.action-bar { display: none !important; }
}
</style>
</head>
<body>
<div class="action-bar" id="actionBar">
<button class="btn btn-secondary" onclick="cetakHalaman()">🖨️ Cetak</button>
</div>
<div class="container" id="isiLaporan">
<div class="logo">SENTARA</div>
<div class="hero">
<div class="hero-left">
<div class="hero-title">LAPORAN ANALISIS SENTIMEN<br>PER BULAN</div>
<div class="hero-sub">SISTEM ANALISIS SENTIMEN WISATA JEMBER</div>
</div>
<div class="hero-right">
<b>Periode</b><br>
<?php echo e($periode->nama ?? '-'); ?><br><br>
<b>Tanggal Cetak</b><br>
<?php echo e(now()->format('d M Y H:i')); ?>
</div>
</div>
<div class="section-title">Ringkasan</div>
<div class="cards">
<div class="card blue">
<div class="card-label">Total Ulasan</div>
<p class="card-value"><?php echo e($totalUlasanKotor); ?></p>
<div class="card-unit">Ulasan Masuk</div>
</div>
<div class="card green">
<div class="card-label">Hasil Analisis</div>
<p class="card-value"><?php echo e($totalUlasan); ?></p>
<div class="card-unit">Data Bersih</div>
</div>
<div class="card yellow">
<div class="card-label">Wisata Dibahas</div>
<p class="card-value"><?php echo e(count($perWisata)); ?></p>
<div class="card-unit">Destinasi</div>
</div>
<div class="card purple">
<div class="card-label">Akurasi Analisis</div>
<p class="card-value"><?php echo e(number_format(($evaluasi->accuracy ?? 0) * 100, 2)); ?>%</p>
<div class="card-unit">Model</div>
</div>
</div>
<div class="section-title">Visualisasi</div>
<div class="chart-row">
<div class="box-pie">
<h4>Distribusi Sentimen</h4>
<canvas id="pieChart" width="220" height="220"></canvas>
</div>
<div class="box-bar">
<h4>Sentimen per Destinasi</h4>
<canvas id="barChart"></canvas>
</div>
</div>
<div class="section-title">Top Wisata Berdasarkan Sentimen Positif</div>
<table>
<tr>
<th>No</th>
<th>Wisata</th>
<th>Total</th>
<th>Positif</th>
<th>Netral</th>
<th>Negatif</th>
<th>% Positif</th>
</tr>
<?php $__currentLoopData = $perWisata; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $i => $w): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<tr>
<td><?php echo e($i + 1); ?></td>
<td><?php echo e($w->wisata); ?></td>
<td><?php echo e($w->total); ?></td>
<td><?php echo e($w->positif); ?></td>
<td><?php echo e($w->netral); ?></td>
<td><?php echo e($w->negatif); ?></td>
<td><span class="badge"><?php echo e($w->total > 0 ? round(($w->positif / $w->total) * 100, 2) : 0); ?>%</span></td>
</tr>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
</table>
<div class="kesimpulan">
<b>KESIMPULAN</b>
<p style="margin:8px 0 0">
Pada periode <b><?php echo e($periode->nama ?? ''); ?></b>,
terdapat <b><?php echo e($totalUlasanKotor); ?></b> ulasan masuk dengan
<b><?php echo e($totalUlasan); ?></b> data berhasil dianalisis.
Sentimen positif mendominasi dengan persentase <b><?php echo e($persen['positif'] ?? 0); ?>%</b>,
diikuti netral <b><?php echo e($persen['netral'] ?? 0); ?>%</b>
dan negatif <b><?php echo e($persen['negatif'] ?? 0); ?>%</b>.
</p>
</div>
<div class="footer">
<div>SENTARA Sistem Analisis Sentimen Wisata Jember</div>
<div>Dicetak oleh: Admin &nbsp;|&nbsp; <?php echo e(now()->format('d M Y H:i')); ?></div>
</div>
</div>
<script>
// ── PIE / DOUGHNUT CHART ──
new Chart(document.getElementById('pieChart'), {
type: 'doughnut',
data: {
labels: ['Positif', 'Netral', 'Negatif'],
datasets: [{
data: [<?php echo e($totalPositif); ?>, <?php echo e($totalNetral); ?>, <?php echo e($totalNegatif); ?>],
backgroundColor: ['#22c55e', '#f59e0b', '#ef4444'],
borderWidth: 2,
borderColor: '#fff'
}]
},
options: {
responsive: false,
animation: false,
plugins: {
legend: { position: 'bottom', labels: { font: { size: 11 }, padding: 10 } }
}
}
});
// ── BAR CHART SENTIMEN PER DESTINASI ──
const wisataLabels = <?php echo json_encode($perWisata->pluck('wisata'), 15, 512) ?>;
const wisataPos = <?php echo json_encode($perWisata->pluck('positif'), 15, 512) ?>;
const wisataNet = <?php echo json_encode($perWisata->pluck('netral'), 15, 512) ?>;
const wisataNeg = <?php echo json_encode($perWisata->pluck('negatif'), 15, 512) ?>;
new Chart(document.getElementById('barChart'), {
type: 'bar',
data: {
labels: wisataLabels,
datasets: [
{ label: 'Positif', data: wisataPos, backgroundColor: '#22c55e' },
{ label: 'Netral', data: wisataNet, backgroundColor: '#f59e0b' },
{ label: 'Negatif', data: wisataNeg, backgroundColor: '#ef4444' }
]
},
options: {
responsive: true,
maintainAspectRatio: true,
animation: false,
plugins: {
legend: { position: 'bottom', labels: { font: { size: 11 } } }
},
scales: {
y: { beginAtZero: true, ticks: { font: { size: 11 } } },
x: { ticks: { font: { size: 10 } } }
}
}
});
// ── CETAK ──
function cetakHalaman() {
window.print();
}
// ── SIMPAN PDF ──
function simpanPDF() {
const el = document.getElementById('isiLaporan');
const bar = document.getElementById('actionBar');
bar.style.display = 'none';
html2pdf().set({
margin: 10,
filename: 'Laporan-<?php echo e(Str::slug($periode->nama ?? "bulanan")); ?>.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true },
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
}).from(el).save().then(() => {
bar.style.display = 'flex';
});
}
</script>
</body>
</html><?php /**PATH C:\laragon\www\SistemAnalisisSentimen\resources\views/laporan/bulanan.blade.php ENDPATH**/ ?>

View File

@ -0,0 +1,605 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Laporan Tahunan</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: Arial, sans-serif;
background: #f3f5fb;
padding: 24px;
color: #1f2937;
}
/* ACTION BAR */
.action-bar {
display: flex;
justify-content: space-between;
align-items: center;
max-width: 960px;
margin: 0 auto 16px;
}
.logo-text {
font-size: 22px;
font-weight: 900;
color: #1e3a8a;
letter-spacing: -1px;
}
.logo-text span { color: #ef4444; }
.btn-group { display: flex; gap: 8px; }
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 9px 18px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
border: none;
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
.btn-blue { background: #1d4ed8; color: white; }
.btn-white { background: white; color: #374151; border: 1px solid #d1d5db; }
.btn:hover { opacity: .88; }
/* CONTAINER */
.container {
max-width: 960px;
margin: 0 auto;
background: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0 4px 20px rgba(0,0,0,0.07);
}
/* HERO */
.hero {
border: 1px solid #dbeafe;
background: linear-gradient(135deg, #eff6ff, #f7f8ff);
border-radius: 12px;
padding: 18px 20px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.hero-left { display: flex; gap: 16px; align-items: center; }
.hero-icon {
width: 52px;
height: 52px;
background: #1d4ed8;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.hero-icon svg { width: 28px; height: 28px; fill: white; }
.hero-title { font-size: 18px; font-weight: 900; color: #132b70; line-height: 1.3; }
.hero-sub { font-size: 11px; color: #6b7280; margin-top: 4px; }
.hero-right { font-size: 12px; text-align: right; line-height: 1.8; }
.hero-right .year { font-size: 22px; font-weight: 800; color: #1e3a8a; }
/* SECTION TITLE */
.section-title {
font-size: 12px;
font-weight: 700;
color: #1e3a8a;
text-transform: uppercase;
letter-spacing: .6px;
margin: 20px 0 10px;
display: flex;
align-items: center;
gap: 8px;
}
.section-title::before {
content: '';
display: inline-block;
width: 4px;
height: 14px;
background: #1d4ed8;
border-radius: 2px;
}
/* CARDS */
.cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-bottom: 20px;
}
.card {
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 14px 16px;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.card-info .card-label { font-size: 11px; color: #6b7280; margin-bottom: 4px; }
.card-info .card-value { font-size: 28px; font-weight: 800; line-height: 1; }
.card-info .card-unit { font-size: 11px; color: #9ca3af; margin-top: 4px; }
.card-icon {
width: 36px; height: 36px;
border-radius: 8px;
display: flex; align-items: center; justify-content: center;
font-size: 18px;
flex-shrink: 0;
}
.blue { background: #eff6ff; border-color: #bfdbfe; }
.green { background: #f0fdf4; border-color: #bbf7d0; }
.yellow { background: #fffbeb; border-color: #fde68a; }
.purple { background: #faf5ff; border-color: #e9d5ff; }
.blue .card-icon { background: #dbeafe; }
.green .card-icon { background: #dcfce7; }
.yellow .card-icon { background: #fef3c7; }
.purple .card-icon { background: #ede9fe; }
/* REKAP + DONUT ROW */
.grid2 {
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 16px;
margin-bottom: 16px;
}
/* TABLE */
.table-box {
border: 1px solid #e5e7eb;
border-radius: 12px;
overflow: hidden;
}
table { width: 100%; border-collapse: collapse; }
table thead tr { background: #f8fafc; }
table th { padding: 10px 12px; font-size: 11px; font-weight: 700; color: #374151; text-align: left; }
table td { padding: 9px 12px; font-size: 11px; border-top: 1px solid #f1f5f9; }
table tbody tr:hover { background: #fafafa; }
.tfoot-row td { background: #f8fafc; font-weight: 700; border-top: 2px solid #e5e7eb; }
.pos { color: #16a34a; font-weight: 600; }
.net { color: #d97706; font-weight: 600; }
.neg { color: #dc2626; font-weight: 600; }
.badge-green {
background: #dcfce7;
color: #15803d;
border-radius: 20px;
padding: 2px 8px;
font-weight: 700;
font-size: 11px;
}
/* DONUT BOX */
.donut-box {
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 16px;
display: flex;
flex-direction: column;
}
.donut-box h4 {
font-size: 11px;
font-weight: 700;
color: #1e3a8a;
text-transform: uppercase;
letter-spacing: .4px;
margin-bottom: 10px;
}
.donut-wrap {
position: relative;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.donut-center {
position: absolute;
text-align: center;
pointer-events: none;
}
.donut-center .dc-num { font-size: 20px; font-weight: 800; color: #1e3a8a; }
.donut-center .dc-label { font-size: 9px; color: #6b7280; }
/* TREND BOX */
.trend-box {
border: 1px solid #e5e7eb;
border-radius: 12px;
padding: 16px;
margin-bottom: 16px;
}
.trend-box h4 {
font-size: 11px;
font-weight: 700;
color: #1e3a8a;
text-transform: uppercase;
letter-spacing: .4px;
margin-bottom: 10px;
}
/* BOTTOM GRID */
.bottom-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 20px;
}
/* KESIMPULAN */
.kesimpulan-box {
border: 1px solid #dbeafe;
background: #eff6ff;
border-radius: 12px;
padding: 16px 18px;
font-size: 12px;
line-height: 1.7;
}
.kesimpulan-box h4 {
font-size: 12px;
font-weight: 700;
color: #1e3a8a;
margin-bottom: 8px;
text-transform: uppercase;
}
/* FOOTER */
.footer {
display: flex;
justify-content: space-between;
font-size: 11px;
color: #9ca3af;
padding-top: 14px;
border-top: 1px solid #f1f5f9;
}
@media print {
body { background: white; padding: 0; }
.action-bar { display: none !important; }
.container { box-shadow: none; }
}
</style>
</head>
<body>
<div class="action-bar no-print" id="actionBar">
<div></div>
<div class="btn-group">
<button class="btn btn-white" onclick="window.print()">🖨 Cetak</button>
</div>
</div>
<div class="container" id="isiLaporan">
<div class="hero">
<div class="hero-left">
<div class="hero-icon">
<svg viewBox="0 0 24 24"><path d="M9 12h6m-6 4h6M7 4H5a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2V6a2 2 0 00-2-2h-2M9 4a2 2 0 002 2h2a2 2 0 002-2M9 4a2 2 0 012-2h2a2 2 0 012 2" stroke="white" stroke-width="2" stroke-linecap="round" fill="none"/></svg>
</div>
<div>
<div style="display:inline-flex;align-items:center;gap:5px;background:#1e3a8a;border-radius:6px;padding:3px 10px;margin-bottom:7px;">
<span style="color:#fff;font-size:11px;font-weight:900;letter-spacing:1px;">-SENTARA-</span>
<span style="color:#93c5fd;font-size:9px;letter-spacing:2px;">JEMBER</span>
</div>
<div class="hero-title">LAPORAN ANALISIS SENTIMEN<br>PER TAHUN</div>
<div class="hero-sub">SISTEM ANALISIS SENTIMEN WISATA JEMBER</div>
</div>
</div>
<div class="hero-right">
<div style="color:#6b7280;font-size:11px;">Tahun</div>
<div class="year"><?php echo e($tahun); ?></div>
<div style="color:#6b7280;font-size:11px;margin-top:6px;">Tanggal Cetak</div>
<div style="font-weight:600;"><?php echo e(now()->format('d M Y H:i')); ?></div>
</div>
</div>
<div class="section-title">Ringkasan Tahunan</div>
<div class="cards">
<div class="card blue">
<div class="card-info">
<div class="card-label">Total Ulasan</div>
<div class="card-value"><?php echo e($totalUlasan); ?></div>
<div class="card-unit">Ulasan</div>
</div>
<div class="card-icon">💬</div>
</div>
<div class="card green">
<div class="card-info">
<div class="card-label">Rata-rata Akurasi</div>
<div class="card-value" style="font-size:20px;"><?php echo e($akurasi); ?>%</div>
<div class="card-unit">Akurasi</div>
</div>
<div class="card-icon"></div>
</div>
<div class="card yellow">
<div class="card-info">
<div class="card-label">Total Wisata Dibahas</div>
<div class="card-value"><?php echo e($totalWisata); ?></div>
<div class="card-unit">Wisata</div>
</div>
<div class="card-icon">📍</div>
</div>
<div class="card purple">
<div class="card-info">
<div class="card-label">Bulan Dianalisis</div>
<div class="card-value"><?php echo e(count($rekapBulanan)); ?></div>
<div class="card-unit">Bulan</div>
</div>
<div class="card-icon">📅</div>
</div>
</div>
<div class="section-title">Rekap Sentimen per Bulan</div>
<div class="grid2">
<div class="table-box">
<table>
<thead>
<tr>
<th>Bulan</th>
<th>Total Ulasan</th>
<th>Positif</th>
<th>Netral</th>
<th>Negatif</th>
<th>Persentase Positif</th>
</tr>
</thead>
<tbody>
<?php $totP=0; $totN=0; $totNt=0; $totAll=0; ?>
<?php $__currentLoopData = $rekapBulanan; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $r): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<?php $totP+=$r->positif; $totN+=$r->negatif; $totNt+=$r->netral; $totAll+=$r->total; ?>
<tr>
<td><?php echo e($r->bulan); ?></td>
<td><?php echo e($r->total); ?></td>
<td class="pos"><?php echo e($r->positif); ?></td>
<td class="net"><?php echo e($r->netral); ?></td>
<td class="neg"><?php echo e($r->negatif); ?></td>
<td><span class="badge-green"><?php echo e(number_format(($r->positif/max($r->total,1))*100,2)); ?>%</span></td>
</tr>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
</tbody>
<tfoot>
<tr class="tfoot-row">
<td>TOTAL / RATA-RATA</td>
<td><?php echo e($totAll); ?></td>
<td class="pos"><?php echo e($totP); ?></td>
<td class="net"><?php echo e($totNt); ?></td>
<td class="neg"><?php echo e($totN); ?></td>
<td><span class="badge-green"><?php echo e($totAll > 0 ? number_format(($totP/$totAll)*100,2) : 0); ?>%</span></td>
</tr>
</tfoot>
</table>
</div>
<div class="donut-box">
<h4>Distribusi Sentimen Tahun <?php echo e($tahun); ?></h4>
<div class="donut-wrap" style="height:220px;">
<canvas id="donut"></canvas>
</div>
</div>
</div>
<div class="section-title">Trend Sentimen per Bulan (<?php echo e($tahun); ?>)</div>
<div class="trend-box">
<canvas id="trend" height="90"></canvas>
</div>
<div class="section-title">Wisata Serpopuler & Kesimpulan</div>
<div class="bottom-grid">
<div class="table-box">
<table>
<thead>
<tr>
<th>No</th>
<th>Wisata</th>
<th>Total Ulasan</th>
<th>Persentase Positif</th>
</tr>
</thead>
<tbody>
<?php $__currentLoopData = $wisataPopuler; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $i => $w): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<tr>
<td><?php echo e($i+1); ?></td>
<td><?php echo e($w->wisata); ?></td>
<td><?php echo e($w->jumlah); ?></td>
<td><span class="badge-green"><?php echo e($w->persen); ?>%</span></td>
</tr>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
</tbody>
</table>
</div>
<div class="kesimpulan-box">
<h4>Kesimpulan Tahunan</h4>
<p>
Secara umum, sentimen positif mendominasi tahun <b><?php echo e($tahun); ?></b>
dengan rata-rata <b><?php echo e($persenPositif); ?>%</b>.
<?php if(isset($wisataPopuler[0])): ?>
<?php echo e($wisataPopuler[0]->wisata); ?> menjadi destinasi paling populer
dengan sentimen positif tertinggi.
<?php endif; ?>
Diperlukan peningkatan pada aspek fasilitas, kebersihan, dan pelayanan
untuk menekan sentimen negatif.
</p>
</div>
</div>
<div class="footer">
<div>SENTARA - Sistem Analisis Sentimen Wisata Jember</div>
<div>Dicetak oleh: <?php echo e(auth()->user()->name); ?> (<?php echo e(ucfirst(auth()->user()->role ?? 'Admin')); ?>)</div>
</div>
</div>
<script>
// ── DONUT ──
const donutCtx = document.getElementById('donut');
const totalUlasan = <?php echo e($totalUlasan > 0 ? $totalUlasan : 1); ?>;
const donutData = [<?php echo e($positif); ?>, <?php echo e($netral); ?>, <?php echo e($negatif); ?>];
// Plugin: angka total di tengah (tidak numpuk)
const centerTextPlugin = {
id: 'centerText',
beforeDraw(chart) {
const { ctx, chartArea: { left, top, right, bottom } } = chart;
const cx = (left + right) / 2;
const cy = (top + bottom) / 2;
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillStyle = '#1e3a8a';
ctx.font = 'bold 22px Arial';
ctx.fillText(totalUlasan, cx, cy - 8);
ctx.fillStyle = '#9ca3af';
ctx.font = '11px Arial';
ctx.fillText('Total Ulasan', cx, cy + 12);
ctx.restore();
}
};
new Chart(donutCtx, {
type: 'doughnut',
data: {
labels: ['Positif', 'Netral', 'Negatif'],
datasets: [{
data: donutData,
backgroundColor: ['#22c55e', '#f59e0b', '#ef4444'],
borderWidth: 3,
borderColor: '#fff',
hoverOffset: 6,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false,
cutout: '62%',
plugins: {
legend: {
position: 'bottom',
labels: {
font: { size: 11 },
padding: 10,
generateLabels(chart) {
const d = chart.data;
return d.labels.map((lbl, i) => {
const val = d.datasets[0].data[i];
const pct = Math.round(val / totalUlasan * 1000) / 10;
return {
text: `${lbl} ${val} (${pct}%)`,
fillStyle: d.datasets[0].backgroundColor[i],
strokeStyle: '#fff',
lineWidth: 2,
index: i,
};
});
}
}
},
tooltip: {
callbacks: {
label(ctx) {
const pct = Math.round(ctx.parsed / totalUlasan * 1000) / 10;
return ` ${ctx.label}: ${ctx.parsed} (${pct}%)`;
}
}
}
}
},
plugins: [centerTextPlugin]
});
// ── TREND LINE ──
new Chart(document.getElementById('trend'), {
type: 'line',
data: {
labels: [
<?php $__currentLoopData = $rekapBulanan; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $r): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
'<?php echo e($r->bulan); ?>',
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
],
datasets: [
{
label: 'Positif',
data: [<?php $__currentLoopData = $rekapBulanan; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $r): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?><?php echo e($r->positif); ?>,<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>],
borderColor: '#22c55e',
backgroundColor: 'rgba(34,197,94,0.08)',
tension: 0.4, fill: true, pointRadius: 4, pointBackgroundColor: '#22c55e'
},
{
label: 'Netral',
data: [<?php $__currentLoopData = $rekapBulanan; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $r): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?><?php echo e($r->netral); ?>,<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>],
borderColor: '#f59e0b',
backgroundColor: 'rgba(245,158,11,0.05)',
tension: 0.4, fill: false, pointRadius: 4, pointBackgroundColor: '#f59e0b',
borderDash: [4,3]
},
{
label: 'Negatif',
data: [<?php $__currentLoopData = $rekapBulanan; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $r): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?><?php echo e($r->negatif); ?>,<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>],
borderColor: '#ef4444',
backgroundColor: 'rgba(239,68,68,0.05)',
tension: 0.4, fill: false, pointRadius: 4, pointBackgroundColor: '#ef4444'
}
]
},
options: {
responsive: true,
animation: false,
plugins: {
legend: { position: 'bottom', labels: { font: { size: 11 }, padding: 12 } }
},
scales: {
y: { beginAtZero: true, ticks: { font: { size: 10 } }, grid: { color: '#f1f5f9' } },
x: { ticks: { font: { size: 10 } }, grid: { display: false } }
}
}
});
// ── SIMPAN PDF ──
function simpanPDF() {
const el = document.getElementById('isiLaporan');
const bar = document.getElementById('actionBar');
bar.style.display = 'none';
html2pdf().set({
margin: 8,
filename: 'Laporan-Tahunan-<?php echo e($tahun); ?>.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true, windowWidth: 960 },
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
}).from(el).save().then(() => {
bar.style.display = 'flex';
});
}
</script>
</body>
</html><?php /**PATH C:\laragon\www\SistemAnalisisSentimen\resources\views/laporan/tahunan.blade.php ENDPATH**/ ?>

View File

@ -1,222 +0,0 @@
<?php
$destinasiList ??= collect([]);
$totalDestinasiAnalisis ??= 0;
$totalDestinasiBerkeluhan ??= 0;
$totalNegatif ??= 0;
$totalUlasan ??= 0;
$persenNegatif ??= 0;
$tingkatKepuasan ??= 0;
$labelKepuasan ??= 'Kurang';
$isuDominan ??= '-';
$isuDominanPersen ??= 0;
$isuUtama ??= [];
$kataDominan ??= [];
$saranPerbaikan ??= [];
$prioritas ??= [];
?>
<div class="space-y-6">
<div class="flex justify-between items-center">
<div>
<h2 class="text-2xl font-bold text-gray-800">Rekomendasi Layanan</h2>
<p class="text-sm text-gray-500">
Rekomendasi dibuat dari ulasan negatif agar saran perbaikan fokus pada keluhan pengunjung.
</p>
<p class="text-xs text-gray-400 mt-1">
<?php echo e($totalDestinasiAnalisis); ?> destinasi dianalisis, <?php echo e($totalDestinasiBerkeluhan); ?> destinasi memiliki ulasan negatif.
</p>
</div>
<form method="GET" action="<?php echo e(route('dashboard')); ?>" class="flex gap-3">
<input type="hidden" name="tab" value="rekomendasi">
<input type="hidden" name="periode_id" value="<?php echo e($periodeAktif->id ?? request('periode_id')); ?>">
<select name="destinasi" onchange="this.form.submit()"
class="border rounded-lg pl-3 pr-9 py-2 text-sm min-w-[220px] focus:outline-none focus:ring-2 focus:ring-blue-400">
<option value="">Semua Destinasi</option>
<?php $__currentLoopData = $destinasiList; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $destinasi): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<option value="<?php echo e($destinasi); ?>" <?php echo e(request('destinasi') == $destinasi ? 'selected' : ''); ?>>
<?php echo e($destinasi); ?>
</option>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
</select>
</form>
</div>
<div class="grid grid-cols-1 md:grid-cols-4 gap-5">
<div class="bg-white rounded-xl shadow p-5 flex items-center gap-4">
<div class="bg-blue-100 text-blue-600 p-3 rounded-full text-xl">📍</div>
<div>
<p class="text-sm text-gray-500">Destinasi Dianalisis</p>
<h3 class="text-xl font-bold"><?php echo e($totalDestinasiAnalisis); ?></h3>
<p class="text-xs text-gray-500"><?php echo e($totalDestinasiBerkeluhan); ?> punya keluhan</p>
</div>
</div>
<div class="bg-white rounded-xl shadow p-5 flex items-center gap-4">
<div class="bg-red-100 text-red-500 p-3 rounded-full text-xl">😟</div>
<div>
<p class="text-sm text-gray-500">Total Ulasan Negatif</p>
<h3 class="text-xl font-bold"><?php echo e($totalNegatif); ?></h3>
<p class="text-xs text-red-500"><?php echo e($persenNegatif); ?>% dari total</p>
</div>
</div>
<div class="bg-white rounded-xl shadow p-5 flex items-center gap-4">
<div class="bg-green-100 text-green-600 p-3 rounded-full text-xl">😊</div>
<div>
<p class="text-sm text-gray-500">Tingkat Kepuasan</p>
<h3 class="text-xl font-bold"><?php echo e($tingkatKepuasan); ?>%</h3>
<span class="text-xs px-2 py-1 rounded
<?php echo e($labelKepuasan === 'Baik' ? 'bg-green-100 text-green-700' :
($labelKepuasan === 'Sedang' ? 'bg-yellow-100 text-yellow-700' :
'bg-red-100 text-red-700')); ?>">
<?php echo e($labelKepuasan); ?>
</span>
</div>
</div>
<div class="bg-white rounded-xl shadow p-5 flex items-center gap-4">
<div class="bg-purple-100 text-purple-600 p-3 rounded-full text-xl"></div>
<div>
<p class="text-sm text-gray-500">Isu Dominan</p>
<h3 class="text-xl font-bold"><?php echo e($isuDominan); ?></h3>
<p class="text-xs text-gray-500"><?php echo e($isuDominanPersen); ?>% dari total isu</p>
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="bg-white rounded-xl shadow p-5">
<h3 class="font-semibold mb-4 text-gray-700">Isu Utama (Top 5)</h3>
<?php $__empty_1 = true; $__currentLoopData = $isuUtama; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $i => $isu): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
<?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',
};
?>
<div class="mb-4">
<div class="flex justify-between text-sm mb-1">
<span><?php echo e($i + 1); ?>. <?php echo e($isu['nama']); ?></span>
<span class="font-semibold"><?php echo e($isu['persen']); ?>%</span>
</div>
<div class="w-full bg-gray-200 h-2 rounded">
<div class="h-2 rounded <?php echo e($warnaClass); ?>" style="width: <?php echo e($isu['persen']); ?>%"></div>
</div>
</div>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
<p class="text-sm text-gray-400 text-center py-4">Belum ada data isu.</p>
<?php endif; ?>
</div>
<div class="bg-white rounded-xl shadow p-5">
<h3 class="font-semibold mb-4 text-gray-700">Kata Kunci Dominan</h3>
<?php $maxFreq = !empty($kataDominan) ? max($kataDominan) : 1; ?>
<?php $__empty_1 = true; $__currentLoopData = array_slice($kataDominan, 0, 5, true); $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $kata => $jumlah): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
<div class="mb-3">
<div class="flex justify-between text-sm">
<span><?php echo e($kata); ?></span>
<span class="font-semibold text-blue-600"><?php echo e($jumlah); ?>x</span>
</div>
<div class="w-full bg-gray-200 h-2 rounded mt-1">
<div class="bg-blue-500 h-2 rounded" style="width: <?php echo e(round(($jumlah / $maxFreq) * 100)); ?>%"></div>
</div>
</div>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
<p class="text-sm text-gray-400 text-center py-4">Belum ada kata kunci.</p>
<?php endif; ?>
</div>
<div class="bg-white rounded-xl shadow p-5">
<h3 class="font-semibold mb-4 text-gray-700">Saran Perbaikan</h3>
<div class="space-y-4 text-sm">
<?php $__empty_1 = true; $__currentLoopData = $saranPerbaikan; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $saran): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
<div class="flex gap-3">
<div class="text-xl"><?php echo e($saran['icon']); ?></div>
<div>
<p class="font-semibold"><?php echo e($saran['nama']); ?></p>
<p class="text-gray-600"><?php echo e($saran['tip']); ?></p>
</div>
</div>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
<p class="text-sm text-gray-400 text-center py-4">Belum ada saran.</p>
<?php endif; ?>
</div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<?php $__empty_1 = true; $__currentLoopData = $prioritas; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $p): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
<?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',
};
?>
<div class="border <?php echo e($border); ?> rounded-xl p-5 bg-white">
<h4 class="font-semibold <?php echo e($title); ?> mb-2">Prioritas <?php echo e($p['rank']); ?></h4>
<h3 class="text-lg font-bold mb-3"><?php echo e($p['nama']); ?></h3>
<ul class="text-sm space-y-2 mb-4">
<?php $__currentLoopData = $p['actions']; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $action): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<li class="flex items-center gap-2">
<span class="text-blue-500"></span> <?php echo e($action); ?>
</li>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
</ul>
<div class="text-xs <?php echo e($dampak); ?> p-2 rounded">
Dampak: <?php echo e($p['dampak']); ?>
</div>
</div>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
<div class="col-span-3 text-center text-gray-400 text-sm py-6">
Belum ada data rekomendasi. Pastikan analisis sentimen sudah dijalankan.
</div>
<?php endif; ?>
</div>
</div><?php /**PATH C:\laragon\www\SistemAnalisisSentimen\resources\views/rekomendasi/index.blade.php ENDPATH**/ ?>

View File

@ -0,0 +1,340 @@
<?php $__env->startSection('content'); ?>
<div class="max-w-7xl mx-auto space-y-6">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div>
<h2 class="text-2xl font-bold text-gray-800">
Riwayat Analisis
</h2>
<p class="text-sm text-gray-500">
Periode tersimpan otomatis setiap kali Ambil Data dijalankan.
</p>
</div>
<form
action="<?php echo e(route('laporan.tahunan')); ?>"
method="GET"
class="flex items-center gap-3">
<div class="relative">
<select
name="tahun"
required
class="appearance-none
border-2 border-blue-500
rounded-2xl
pl-4 pr-9
py-2.5
text-sm font-medium
bg-white
shadow-sm
cursor-pointer
focus:outline-none
focus:ring-2
focus:ring-blue-300">
<?php for($tahun = date('Y'); $tahun >= 2024; $tahun--): ?>
<option value="<?php echo e($tahun); ?>"><?php echo e($tahun); ?></option>
<?php endfor; ?>
</select>
</div>
<button
type="submit"
class="bg-blue-600
hover:bg-blue-700
text-white
px-5 py-2.5
rounded-2xl
text-sm font-medium
shadow">
Cetak Tahunan
</button>
</form>
</div>
<?php if(session('success')): ?>
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 rounded">
<?php echo e(session('success')); ?>
</div>
<?php endif; ?>
<?php if(session('error')): ?>
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded whitespace-pre-line">
<?php echo e(session('error')); ?>
</div>
<?php endif; ?>
<div class="bg-white rounded-xl shadow p-6">
<h3 class="font-semibold text-gray-800 mb-1">
Import Data Lama dari CSV
</h3>
<p class="text-sm text-gray-500 mb-4">
Gunakan untuk memasukkan ulasan historis. Kolom wajib:
wisata, rating, ulasan, tanggal.
Kolom reviewer boleh ada.
</p>
<form
action="<?php echo e(route('riwayat.import')); ?>"
method="POST"
enctype="multipart/form-data"
class="grid grid-cols-1 md:grid-cols-[180px_1fr_auto] gap-3 md:items-center">
<?php echo csrf_field(); ?>
<input
type="month"
name="periode_bulan"
value="<?php echo e(now()->format('Y-m')); ?>"
class="border rounded-lg px-3 py-2 text-sm bg-white shadow-sm min-w-[150px]"
required>
<input
type="file"
name="file"
accept=".csv,.txt"
class="border rounded-lg px-3 py-2 text-sm bg-white shadow-sm min-w-0"
required>
<button
class="bg-green-600 hover:bg-green-700 text-white px-5 py-2 rounded-lg text-sm shadow">
Import CSV
</button>
</form>
<?php if($errors->any()): ?>
<div class="mt-3 text-sm text-red-600">
<?php echo e($errors->first()); ?>
</div>
<?php endif; ?>
</div>
<div class="bg-white rounded-xl shadow overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead class="bg-gray-100 text-left">
<tr>
<th class="px-4 py-3">
Periode
</th>
<th class="px-4 py-3 text-center">
Total Ulasan
</th>
<th class="px-4 py-3 text-center">
Hasil Analisis
</th>
<th class="px-4 py-3 text-center">
Wisata
</th>
<th class="px-4 py-3">
Terakhir Analisis
</th>
<th class="px-4 py-3 text-center">
Aksi
</th>
</tr>
</thead>
<tbody class="divide-y">
<?php $__empty_1 = true; $__currentLoopData = $riwayat; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 font-semibold text-gray-800">
<?php echo e($item->nama); ?>
</td>
<td class="px-4 py-3 text-center">
<?php echo e($item->total_ulasan); ?>
</td>
<td class="px-4 py-3 text-center">
<?php echo e($item->total_hasil); ?>
</td>
<td class="px-4 py-3 text-center">
<?php echo e($item->total_wisata); ?>
</td>
<td class="px-4 py-3 text-gray-500">
<?php echo e($item->terakhir_analisis
? \Carbon\Carbon::parse(
$item->terakhir_analisis
)->format('d M Y H:i')
: '-'); ?>
</td>
<td class="px-4 py-3">
<div class="flex flex-wrap justify-center gap-2">
<a
href="<?php echo e(route('dashboard',
['periode_id'=>$item->id,
'tab'=>'analisis'])); ?>"
class="px-3 py-1 rounded-lg
bg-green-50 text-green-600
hover:bg-green-100">
Analisis
</a>
<a
href="<?php echo e(route('ulasan.index',
['periode_id'=>$item->id])); ?>"
class="px-3 py-1 rounded-lg
bg-gray-100 text-gray-700
hover:bg-gray-200">
Ulasan
</a>
<a
href="<?php echo e(route('laporan.bulanan', $item->id)); ?>"
class="px-3 py-1 rounded-lg
bg-blue-50 text-blue-600
hover:bg-blue-100">
Cetak Laporan
</a>
</div>
</td>
</tr>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
<tr>
<td
colspan="6"
class="px-4 py-8 text-center text-gray-400">
Belum ada riwayat analisis.
Jalankan Ambil Data terlebih dahulu.
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
<?php if($riwayat->hasPages()): ?>
<?php
$current = $riwayat->currentPage();
$last = $riwayat->lastPage();
$query = http_build_query(request()->except('page'));
$window = collect(range(max(1, $current - 2), min($last, $current + 2)));
?>
<div class="px-6 py-4 border-t border-gray-100 flex items-center justify-between text-sm text-gray-600">
<p>Menampilkan <?php echo e($riwayat->firstItem()); ?> <?php echo e($riwayat->lastItem()); ?> dari <?php echo e($riwayat->total()); ?> data</p>
<div class="flex items-center gap-1">
<?php if($riwayat->onFirstPage()): ?>
<span class="px-3 py-1.5 rounded-lg border bg-gray-100 text-gray-400 cursor-not-allowed">&laquo;</span>
<?php else: ?>
<a href="<?php echo e($riwayat->previousPageUrl()); ?>&<?php echo e($query); ?>" class="px-3 py-1.5 rounded-lg border hover:bg-blue-50 text-blue-600 transition">&laquo;</a>
<?php endif; ?>
<?php if(!$window->contains(1)): ?>
<a href="<?php echo e($riwayat->url(1)); ?>&<?php echo e($query); ?>" class="px-3 py-1.5 rounded-lg border hover:bg-blue-50 text-blue-600 transition">1</a>
<?php if($window->min() > 2): ?>
<span class="px-2 py-1.5 text-gray-400">...</span>
<?php endif; ?>
<?php endif; ?>
<?php $__currentLoopData = $window; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $page): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<?php if($page == $current): ?>
<span class="px-3 py-1.5 rounded-lg border bg-blue-600 text-white font-semibold"><?php echo e($page); ?></span>
<?php else: ?>
<a href="<?php echo e($riwayat->url($page)); ?>&<?php echo e($query); ?>" class="px-3 py-1.5 rounded-lg border hover:bg-blue-50 text-blue-600 transition"><?php echo e($page); ?></a>
<?php endif; ?>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
<?php if(!$window->contains($last)): ?>
<?php if($window->max() < $last - 1): ?>
<span class="px-2 py-1.5 text-gray-400">...</span>
<?php endif; ?>
<a href="<?php echo e($riwayat->url($last)); ?>&<?php echo e($query); ?>" class="px-3 py-1.5 rounded-lg border hover:bg-blue-50 text-blue-600 transition"><?php echo e($last); ?></a>
<?php endif; ?>
<?php if($riwayat->hasMorePages()): ?>
<a href="<?php echo e($riwayat->nextPageUrl()); ?>&<?php echo e($query); ?>" class="px-3 py-1.5 rounded-lg border hover:bg-blue-50 text-blue-600 transition">&raquo;</a>
<?php else: ?>
<span class="px-3 py-1.5 rounded-lg border bg-gray-100 text-gray-400 cursor-not-allowed">&raquo;</span>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php $__env->stopSection(); ?>
<?php echo $__env->make('layouts.sentara', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH C:\laragon\www\SistemAnalisisSentimen\resources\views/ulasan/riwayat.blade.php ENDPATH**/ ?>

View File

@ -1,354 +0,0 @@
<?php $__env->startSection('content'); ?>
<div class="max-w-7xl mx-auto">
<?php if(session('success')): ?>
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mb-6 rounded">
<?php echo e(session('success')); ?>
</div>
<?php endif; ?>
<?php if(session('error')): ?>
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-6 rounded whitespace-pre-line">
<?php echo e(session('error')); ?>
</div>
<?php endif; ?>
<?php if($noDataPesan ?? null): ?>
<div class="bg-blue-50 border-l-4 border-blue-400 text-blue-700 p-4 mb-6 rounded flex items-center gap-2">
<span>📅</span> <span><?php echo e($noDataPesan); ?></span>
</div>
<?php endif; ?>
<?php if($periodeAktif && !$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>
<?php endif; ?>
<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 aktif: <?php echo e($periodeAktif->nama ?? 'Belum ada data'); ?>
</span>
<a href="<?php echo e(route('riwayat.index')); ?>" class="text-sm text-blue-600 hover:underline">
Lihat riwayat
</a>
</div>
<div class="flex items-center gap-3">
<?php if($periodeAktif): ?>
<span class="text-xs text-gray-400">
Terakhir update: <?php echo e($lastUpdate ? \Carbon\Carbon::parse($lastUpdate)->format('d M Y H:i') : '-'); ?>
</span>
<?php endif; ?>
<form method="GET"
action="<?php echo e(route('dashboard')); ?>"
class="flex flex-col md:flex-row gap-3 md:items-center">
<input type="month"
name="periode_bulan"
value="<?php echo e(request('periode_bulan', now()->format('Y-m'))); ?>"
max="<?php echo e(now()->format('Y-m')); ?>"
onchange="this.form.submit()"
class="border rounded-lg px-3 py-2 text-sm bg-white shadow-sm min-w-[150px]">
</form>
</div>
</div>
<div x-data="{ tab: new URLSearchParams(window.location.search).get('tab') || 'dashboard' }"
x-init="$nextTick(() => { if (tab === 'dashboard') renderAllCharts() })"
class="mb-6">
<!-- TAB -->
<div class="bg-blue-700 rounded-xl p-1 flex space-x-2 shadow mb-6">
<button @click="tab='dashboard'; $nextTick(() => renderAllCharts())"
: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'"
: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'"
: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'">
<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"><?php echo e($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"><?php echo e($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"><?php echo e($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"><?php echo e($stats['netral_persen']); ?>%</h3>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<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>
<div class="bg-white rounded-xl shadow p-6">
<h3 class="font-semibold mb-4 text-gray-700">Sentimen per Destinasi</h3>
<div class="h-[300px]">
<canvas id="destinationChart"></canvas>
</div>
</div>
</div>
<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">
<?php $__currentLoopData = $totalPerWisata; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<div class="flex justify-between border-b pb-3">
<span><?php echo e($item->wisata); ?></span>
<span class="font-semibold text-gray-800"><?php echo e($item->total); ?> ulasan</span>
</div>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
</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 flex flex-wrap content-center justify-center gap-3 text-center">
<p class="text-sm text-gray-400">Belum ada kata dominan.</p>
</div>
</div>
</div>
</div>
<!-- ================= ANALISIS ================= -->
<div x-show="tab === 'analisis'" id="analisis-content">
<?php echo $__env->make('analisis.index', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?>
</div>
<!-- ================= REKOMENDASI ================= -->
<div x-show="tab === 'rekomendasi'" id="rekomendasi-content">
<?php echo $__env->make('rekomendasi.index', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const pieData = <?php echo json_encode($chartSentimen, 15, 512) ?>;
const destinationData = <?php echo json_encode($chartDestinasi, 15, 512) ?>;
const text = <?php echo json_encode($allText, 15, 512) ?>;
function renderAllCharts() {
// PIE CHART
const pieCanvas = document.getElementById('pieChart');
if (pieCanvas) {
if (window.pieChartInstance) {
window.pieChartInstance.destroy();
window.pieChartInstance = null;
}
// Reset canvas dengan clone agar benar-benar bersih
const newPie = pieCanvas.cloneNode(false);
pieCanvas.parentNode.replaceChild(newPie, pieCanvas);
window.pieChartInstance = new Chart(newPie, { // <-- newPie, bukan pieCanvas
type: 'pie',
data: {
labels: pieData.map(i => i.sentimen),
datasets: [{
data: pieData.map(i => Number(i.total)),
backgroundColor: ['#3B82F6', '#EF4444', '#F59E0B']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false
}
});
}
// BAR CHART
const destinationCanvas = document.getElementById('destinationChart');
if (destinationCanvas) {
if (window.destinationChartInstance) {
window.destinationChartInstance.destroy();
window.destinationChartInstance = null;
}
window.destinationChartInstance = new Chart(destinationCanvas, {
type: 'bar',
data: {
labels: destinationData.map(item => item.wisata),
datasets: [
{
label: 'Positif',
data: destinationData.map(item => Number(item.positif || 0)),
backgroundColor: 'rgba(96, 165, 250, 0.88)',
borderColor: '#3B82F6',
borderWidth: 1,
borderRadius: 3,
},
{
label: 'Negatif',
data: destinationData.map(item => Number(item.negatif || 0)),
backgroundColor: 'rgba(248, 113, 113, 0.9)',
borderColor: '#EF4444',
borderWidth: 1,
borderRadius: 3,
},
{
label: 'Netral',
data: destinationData.map(item => Number(item.netral || 0)),
backgroundColor: 'rgba(251, 191, 36, 0.72)',
borderColor: '#F59E0B',
borderWidth: 1,
borderRadius: 3,
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
animation: false,
plugins: {
legend: {
position: 'top',
align: 'center',
labels: { boxWidth: 38, boxHeight: 10, color: '#4B5563', font: { size: 12 } },
},
tooltip: {
callbacks: {
label: context => `${context.dataset.label}: ${context.parsed.y} ulasan`,
},
},
},
scales: {
x: {
grid: { color: 'rgba(156, 163, 175, 0.22)' },
ticks: { color: '#4B5563', maxRotation: 14, minRotation: 14, font: { size: 12 } },
},
y: {
beginAtZero: true,
ticks: { color: '#4B5563', precision: 0 },
grid: { color: 'rgba(156, 163, 175, 0.28)' },
},
},
categoryPercentage: 0.72,
barPercentage: 0.82,
},
});
}
// WORD CLOUD
const wordCloud = document.getElementById('wordCloud');
const stopwords = new Set([
'yang','dan','dari','untuk','dengan','ini','itu','tidak','ada','juga',
'sangat','lebih','sudah','bisa','tapi','karena','pada','atau','saya',
'kami','mereka','nya','jadi','kalau','dalam','akan','saat','tempat'
]);
if (wordCloud && text.trim().length > 0) {
const wordCount = {};
text.toLowerCase().split(/\s+/).forEach(word => {
const cleanWord = word.replace(/[^a-z]/g, '');
if (cleanWord.length > 3 && !stopwords.has(cleanWord)) {
wordCount[cleanWord] = (wordCount[cleanWord] || 0) + 1;
}
});
const words = Object.entries(wordCount)
.sort((a, b) => b[1] - a[1])
.slice(0, 28);
if (words.length > 0) {
const max = words[0][1] || 1;
const colors = ['text-blue-700', 'text-green-600', 'text-red-500', 'text-yellow-600', 'text-indigo-600'];
wordCloud.innerHTML = words.map(([word, count], index) => {
const size = 13 + Math.round((count / max) * 20);
const weight = count === max ? 800 : 600;
return `<span class="${colors[index % colors.length]} inline-block" style="font-size:${size}px;font-weight:${weight}" title="${count}x">${word}</span>`;
}).join('');
}
}
}
</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>
<?php $__env->stopSection(); ?>
<?php echo $__env->make('layouts.sentara', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH C:\laragon\www\SistemAnalisisSentimen\resources\views/dashboard.blade.php ENDPATH**/ ?>

View File

@ -1,229 +0,0 @@
<?php if($standalone ?? false): ?>
<?php $__env->startSection('content'); ?>
<?php endif; ?>
<?php
$totalHasilAnalisis ??= $hasil->total() ?? 0;
$jumlahKelasAnalisis ??= 0;
$evaluasiTersedia = $evaluasi && $jumlahKelasAnalisis >= 2;
?>
<div class="space-y-4">
<div class="flex justify-between items-center">
<div>
<h2 class="text-2xl font-bold">Hasil Analisis Sentimen</h2>
<p class="text-gray-500 text-sm">
Evaluasi memakai pseudo-label dari rating/rule otomatis, bukan anotasi manual.
</p>
<?php if($totalHasilAnalisis > 0 && !$evaluasiTersedia): ?>
<p class="text-sm text-yellow-700 bg-yellow-50 border border-yellow-200 rounded-lg px-3 py-2 mt-3">
Semua hasil hanya memiliki <?php echo e($jumlahKelasAnalisis); ?> kelas sentimen. Precision, recall, F1, dan akurasi tidak dihitung karena evaluasi klasifikasi membutuhkan minimal 2 kelas. Detail hasil analisis tetap ditampilkan di bawah.
</p>
<?php endif; ?>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div class="bg-white p-5 rounded-xl shadow">
<p class="text-gray-500 text-sm">Precision</p>
<h3 class="text-2xl font-bold text-blue-600">
<?php echo e($evaluasiTersedia ? number_format($evaluasi->precision ?? 0, 2) : '-'); ?>
</h3>
</div>
<div class="bg-white p-5 rounded-xl shadow">
<p class="text-gray-500 text-sm">Recall</p>
<h3 class="text-2xl font-bold text-green-600">
<?php echo e($evaluasiTersedia ? number_format($evaluasi->recall ?? 0, 2) : '-'); ?>
</h3>
</div>
<div class="bg-white p-5 rounded-xl shadow">
<p class="text-gray-500 text-sm">F1 Score</p>
<h3 class="text-2xl font-bold text-purple-600">
<?php echo e($evaluasiTersedia ? number_format($evaluasi->f1_score ?? 0, 2) : '-'); ?>
</h3>
</div>
<div class="bg-white p-5 rounded-xl shadow">
<p class="text-gray-500 text-sm">Akurasi</p>
<h3 class="text-2xl font-bold text-indigo-600">
<?php echo e($evaluasiTersedia ? number_format($evaluasi->accuracy ?? 0, 2) : '-'); ?>
</h3>
</div>
</div>
<div class="flex items-end gap-3 mb-4">
<form id="filter-form"
method="GET"
action="<?php echo e(route('dashboard')); ?>"
class="flex items-end gap-2">
<input type="hidden" name="tab" value="analisis">
<input type="hidden" name="periode_id"
value="<?php echo e($periodeAktif->id ?? ''); ?>">
<select
name="wisata"
class="border rounded px-3 py-2 h-[46px]">
<option value="">Semua Destinasi</option>
<?php $__currentLoopData = $wisataList; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<option value="<?php echo e($item); ?>"
<?php echo e(request('wisata') == $item ? 'selected' : ''); ?>>
<?php echo e($item); ?>
</option>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
</select>
<button
type="submit"
class="bg-blue-600 text-white px-4 rounded h-[46px]">
Filter
</button>
</form>
<a href="<?php echo e(route('riwayat.index')); ?>"
class="inline-flex items-center justify-center bg-blue-600 hover:bg-blue-700 text-white px-5 rounded-lg shadow transition h-[46px]">
<i class="fas fa-history mr-2"></i>
Lihat Riwayat
</a>
</div>
<div class="bg-white rounded-xl shadow p-4 h-fit">
<h3 class="font-semibold mb-4">Detail Hasil Analisis Sentimen</h3>
<table class="w-full text-sm">
<thead class="border-b text-left">
<tr>
<th>No</th>
<th>Wisata</th>
<th>Ulasan Asli</th>
<th>Ulasan Bersih</th>
<th>Sentimen</th>
<th>Probabilitas</th>
</tr>
</thead>
<tbody>
<?php $__empty_1 = true; $__currentLoopData = $hasil; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $item): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); $__empty_1 = false; ?>
<tr class="border-b">
<td><?php echo e(($hasil->currentPage() - 1) * $hasil->perPage() + $loop->iteration); ?></td>
<td><?php echo e($item->wisata); ?></td>
<td class="text-gray-500"><?php echo e(Str::limit($item->ulasan_asli ?? '-', 80)); ?></td>
<td><?php echo e(Str::limit($item->hasil_preprocessing ?? '-', 80)); ?></td>
<td>
<span class="px-2 py-1 rounded text-xs
<?php echo e(strtolower($item->sentimen) == 'positif' ? 'bg-green-100 text-green-600' : ''); ?>
<?php echo e(strtolower($item->sentimen) == 'negatif' ? 'bg-red-100 text-red-600' : ''); ?>
<?php echo e(strtolower($item->sentimen) == 'netral' ? 'bg-yellow-100 text-yellow-600' : ''); ?>
">
<?php echo e(ucfirst($item->sentimen)); ?>
</span>
</td>
<td><?php echo e(number_format($item->probabilitas ?? 0, 2)); ?></td>
</tr>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); if ($__empty_1): ?>
<tr>
<td colspan="6" class="text-center py-4 text-gray-500">Tidak ada data</td>
</tr>
<?php endif; ?>
</tbody>
</table>
<?php 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)));
?>
<div class="mt-4 flex items-center justify-between text-sm text-gray-600">
<p>Menampilkan <?php echo e($hasil->firstItem()); ?> - <?php echo e($hasil->lastItem()); ?> dari <?php echo e($hasil->total()); ?> data</p>
<div class="flex items-center gap-1">
<?php if($hasil->onFirstPage()): ?>
<span class="px-3 py-1 rounded border bg-gray-100 text-gray-400 cursor-not-allowed">&laquo;</span>
<?php else: ?>
<a href="<?php echo e($hasil->previousPageUrl()); ?>&<?php echo e($query); ?>" class="px-3 py-1 rounded border hover:bg-blue-50 text-blue-600">&laquo;</a>
<?php endif; ?>
<?php if(!$window->contains(1)): ?>
<a href="<?php echo e($hasil->url(1)); ?>&<?php echo e($query); ?>" class="px-3 py-1 rounded border hover:bg-blue-50 text-blue-600">1</a>
<?php if($window->min() > 2): ?>
<span class="px-2 py-1 text-gray-400">...</span>
<?php endif; ?>
<?php endif; ?>
<?php $__currentLoopData = $window; $__env->addLoop($__currentLoopData); foreach($__currentLoopData as $page): $__env->incrementLoopIndices(); $loop = $__env->getLastLoop(); ?>
<?php if($page == $current): ?>
<span class="px-3 py-1 rounded border bg-blue-600 text-white font-semibold"><?php echo e($page); ?></span>
<?php else: ?>
<a href="<?php echo e($hasil->url($page)); ?>&<?php echo e($query); ?>" class="px-3 py-1 rounded border hover:bg-blue-50 text-blue-600"><?php echo e($page); ?></a>
<?php endif; ?>
<?php endforeach; $__env->popLoop(); $loop = $__env->getLastLoop(); ?>
<?php if(!$window->contains($last)): ?>
<?php if($window->max() < $last - 1): ?>
<span class="px-2 py-1 text-gray-400">...</span>
<?php endif; ?>
<a href="<?php echo e($hasil->url($last)); ?>&<?php echo e($query); ?>" class="px-3 py-1 rounded border hover:bg-blue-50 text-blue-600"><?php echo e($last); ?></a>
<?php endif; ?>
<?php if($hasil->hasMorePages()): ?>
<a href="<?php echo e($hasil->nextPageUrl()); ?>&<?php echo e($query); ?>" class="px-3 py-1 rounded border hover:bg-blue-50 text-blue-600">&raquo;</a>
<?php else: ?>
<span class="px-3 py-1 rounded border bg-gray-100 text-gray-400 cursor-not-allowed">&raquo;</span>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php if($standalone ?? false): ?>
<?php $__env->stopSection(); ?>
<?php endif; ?>
<?php echo $__env->make('layouts.sentara', array_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]))->render(); ?><?php /**PATH C:\laragon\www\SistemAnalisisSentimen\resources\views/analisis/index.blade.php ENDPATH**/ ?>

File diff suppressed because one or more lines are too long