MIF_E31231226/resources/views/laporan/bulanan.blade.php

313 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: DejaVu Sans, sans-serif; font-size: 11px; color: #1f2937; background: #fff; }
.header-wrap { position: relative; background: #1a3a5c; color: white; margin-bottom: 0; overflow: hidden; }
.header-bg { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(135deg, #0f2744 0%, #1a4a7a 50%, #0d3060 100%); }
.header-content { position: relative; padding: 0; }
.header-top { display: flex; align-items: center; padding: 12px 20px 8px 20px; border-bottom: 1px solid rgba(255,255,255,0.15); }
.logo-circle { width: 48px; height: 48px; background: #c8a84b; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-right: 12px; flex-shrink: 0; font-size: 14px; font-weight: bold; color: #1a3a5c; }
.logo-img { width: 48px; height: 48px; border-radius: 50%; object-fit: contain; margin-right: 12px; background: white; padding: 3px; }
.org1 { font-size: 11px; font-weight: bold; color: #c8a84b; letter-spacing: .5px; }
.org2 { font-size: 10px; color: rgba(255,255,255,.85); }
.header-body { padding: 14px 20px 16px 20px; }
.header-title { font-size: 24px; font-weight: bold; color: white; letter-spacing: 1px; text-transform: uppercase; line-height: 1.1; }
.header-sub { font-size: 14px; color: #c8a84b; font-weight: bold; letter-spacing: .5px; margin-bottom: 8px; text-transform: uppercase; }
.header-desc { font-size: 9.5px; color: rgba(255,255,255,.75); margin-bottom: 12px; line-height: 1.5; }
.periode-badge { display: inline-block; background: #1d4ed8; border-radius: 6px; padding: 7px 14px; }
.pb-label { font-size: 9px; color: rgba(255,255,255,.7); }
.pb-value { font-size: 13px; font-weight: bold; color: white; }
.page-body { padding: 16px 20px; }
.section { margin-bottom: 14px; }
.section-title { display: flex; align-items: center; gap: 7px; font-size: 11px; font-weight: bold; color: #1d4ed8; letter-spacing: .5px; text-transform: uppercase; margin-bottom: 10px; padding-bottom: 5px; border-bottom: 2px solid #dbeafe; }
.stats { display: flex; gap: 8px; }
.stat-card { flex: 1; background: white; border: 1px solid #e5e7eb; border-radius: 8px; padding: 10px 12px; display: flex; align-items: center; gap: 8px; }
.stat-icon { width: 34px; height: 34px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 16px; flex-shrink: 0; }
.stat-icon.blue { background: #eff6ff; } .stat-icon.green { background: #f0fdf4; } .stat-icon.red { background: #fef2f2; } .stat-icon.yellow { background: #fefce8; }
.slabel { font-size: 8.5px; color: #6b7280; }
.sval { font-size: 18px; font-weight: bold; line-height: 1.1; }
.spct { font-size: 8.5px; }
.sval.blue { color: #1d4ed8; } .sval.green { color: #16a34a; } .sval.red { color: #dc2626; } .sval.yellow { color: #d97706; }
.spct.green { color: #16a34a; } .spct.red { color: #dc2626; } .spct.yellow { color: #d97706; }
.two-col { display: flex; gap: 12px; }
.col-left { flex: 1; } .col-right { flex: 1; }
.donut-wrap { display: flex; align-items: center; gap: 16px; padding: 8px 0; }
.donut-chart { width: 100px; height: 100px; flex-shrink: 0; }
.donut-svg { width: 100px; height: 100px; transform: rotate(-90deg); }
.legend-item { display: flex; align-items: center; gap: 6px; margin-bottom: 5px; }
.legend-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.legend-label { font-size: 9.5px; color: #374151; flex: 1; }
.legend-val { font-size: 9.5px; font-weight: bold; color: #111; }
table { width: 100%; border-collapse: collapse; }
.table-wrap { border-radius: 8px; overflow: hidden; border: 1px solid #e5e7eb; }
thead tr { background: #1d4ed8; }
thead th { color: white; font-size: 9.5px; font-weight: bold; padding: 7px 10px; text-align: left; }
thead th.center { text-align: center; }
tbody tr:nth-child(even) { background: #f8fafc; }
tbody td { font-size: 9.5px; padding: 6px 10px; border-bottom: 1px solid #f1f5f9; color: #374151; }
tbody td.center { text-align: center; }
tfoot td { background: #eff6ff; font-weight: bold; color: #1d4ed8; font-size: 9.5px; padding: 6px 10px; }
.badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 8.5px; font-weight: bold; }
.badge-positif { background: #dcfce7; color: #15803d; }
.badge-negatif { background: #fee2e2; color: #b91c1c; }
.badge-netral { background: #fef9c3; color: #92400e; }
.rekom-item { display: flex; align-items: flex-start; gap: 10px; background: #f8fafc; border-radius: 8px; padding: 8px 10px; border: 1px solid #e5e7eb; margin-bottom: 6px; }
.rekom-icon { width: 28px; height: 28px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0; }
.ri-red { background: #fee2e2; } .ri-orange { background: #ffedd5; } .ri-green { background: #dcfce7; } .ri-blue { background: #dbeafe; } .ri-purple { background: #ede9fe; }
.rtitle { font-weight: bold; font-size: 10px; color: #111827; margin-bottom: 2px; }
.rtip { font-size: 9px; color: #6b7280; line-height: 1.4; }
.eval-grid { display: flex; gap: 8px; }
.eval-box { flex: 1; background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 8px; padding: 9px 8px; text-align: center; }
.eval-label { font-size: 8.5px; color: #3b82f6; margin-bottom: 3px; }
.eval-val { font-size: 16px; font-weight: bold; color: #1d4ed8; }
.metodologi { background: #1a3a5c; color: white; padding: 14px 20px; display: flex; justify-content: space-between; align-items: flex-start; gap: 20px; margin-top: 16px; }
.metod-left h4 { font-size: 10px; font-weight: bold; color: #c8a84b; margin-bottom: 5px; }
.metod-left p { font-size: 8.5px; color: rgba(255,255,255,.75); line-height: 1.5; }
.metod-right { text-align: right; flex-shrink: 0; }
.mdate { font-size: 9px; color: rgba(255,255,255,.7); }
.msys { font-size: 9.5px; font-weight: bold; color: #c8a84b; margin-top: 3px; }
.page-break { page-break-before: always; }
.no-break { page-break-inside: avoid; }
</style>
</head>
<body>
{{-- HEADER --}}
<div class="header-wrap">
<div class="header-bg"></div>
<div class="header-content">
<div class="header-top">
@php $logoPath = public_path('images/logo-sentara.png'); @endphp
@if(file_exists($logoPath))
<img src="{{ $logoPath }}" class="logo-img" alt="Logo">
@else
<div class="logo-circle">DISPAR</div>
@endif
<div>
<div class="org1">DINAS PARIWISATA</div>
<div class="org2">KABUPATEN JEMBER</div>
</div>
</div>
<div class="header-body">
<div class="header-title">LAPORAN ANALISIS SENTIMEN</div>
<div class="header-sub">Wisata Kabupaten Jember</div>
<div class="header-desc">Laporan ini berisi hasil analisis sentimen terhadap ulasan wisata dari Google Maps menggunakan metode Naive Bayes.</div>
<div class="periode-badge">
<div class="pb-label">Periode Analisis</div>
<div class="pb-value">{{ $periode->nama }}</div>
</div>
</div>
</div>
</div>
<div class="page-body">
{{-- RINGKASAN --}}
<div class="section no-break">
<div class="section-title">📊 RINGKASAN SENTIMEN</div>
<div class="stats">
<div class="stat-card">
<div class="stat-icon blue">💬</div>
<div><div class="slabel">Total Ulasan</div><div class="sval blue">{{ $totalUlasan }}</div><div class="spct" style="color:#6b7280">100% dari keseluruhan</div></div>
</div>
<div class="stat-card">
<div class="stat-icon green">😊</div>
<div><div class="slabel">Sentimen Positif</div><div class="sval green">{{ $totalPositif }}</div><div class="spct green">{{ $persen['positif'] }}%</div></div>
</div>
<div class="stat-card">
<div class="stat-icon red">😟</div>
<div><div class="slabel">Sentimen Negatif</div><div class="sval red">{{ $totalNegatif }}</div><div class="spct red">{{ $persen['negatif'] }}%</div></div>
</div>
<div class="stat-card">
<div class="stat-icon yellow">😐</div>
<div><div class="slabel">Sentimen Netral</div><div class="sval yellow">{{ $totalNetral }}</div><div class="spct yellow">{{ $persen['netral'] }}%</div></div>
</div>
</div>
</div>
{{-- DISTRIBUSI + EVALUASI --}}
<div class="two-col no-break">
<div class="col-left">
<div class="section-title">🍩 DISTRIBUSI SENTIMEN</div>
@php
$t = max($totalUlasan, 1);
$r = 38; $circ = 2 * 3.14159 * $r;
$pP = $totalPositif / $t;
$pN = $totalNegatif / $t;
$pNt = $totalNetral / $t;
@endphp
<div class="donut-wrap">
<div class="donut-chart">
<svg class="donut-svg" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="{{ $r }}" fill="none" stroke="#e5e7eb" stroke-width="18"/>
@if($pP > 0)<circle cx="50" cy="50" r="{{ $r }}" fill="none" stroke="#22c55e" stroke-width="18" stroke-dasharray="{{ $circ*$pP }} {{ $circ*(1-$pP) }}" stroke-dashoffset="0"/>@endif
@if($pN > 0)<circle cx="50" cy="50" r="{{ $r }}" fill="none" stroke="#ef4444" stroke-width="18" stroke-dasharray="{{ $circ*$pN }} {{ $circ*(1-$pN) }}" stroke-dashoffset="-{{ $circ*$pP }}"/>@endif
@if($pNt > 0)<circle cx="50" cy="50" r="{{ $r }}" fill="none" stroke="#f59e0b" stroke-width="18" stroke-dasharray="{{ $circ*$pNt }} {{ $circ*(1-$pNt) }}" stroke-dashoffset="-{{ $circ*($pP+$pN) }}"/>@endif
<circle cx="50" cy="50" r="22" fill="white"/>
</svg>
</div>
<div>
<div class="legend-item"><div class="legend-dot" style="background:#22c55e"></div><span class="legend-label">Positif</span><span class="legend-val">{{ $persen['positif'] }}% ({{ $totalPositif }})</span></div>
<div class="legend-item"><div class="legend-dot" style="background:#ef4444"></div><span class="legend-label">Negatif</span><span class="legend-val">{{ $persen['negatif'] }}% ({{ $totalNegatif }})</span></div>
<div class="legend-item"><div class="legend-dot" style="background:#f59e0b"></div><span class="legend-label">Netral</span><span class="legend-val">{{ $persen['netral'] }}% ({{ $totalNetral }})</span></div>
</div>
</div>
</div>
<div class="col-right">
<div class="section-title">🎯 EVALUASI MODEL</div>
@if($evaluasi)
<div class="eval-grid">
<div class="eval-box"><div class="eval-label">Precision</div><div class="eval-val">{{ number_format($evaluasi->precision,2) }}</div></div>
<div class="eval-box"><div class="eval-label">Recall</div><div class="eval-val">{{ number_format($evaluasi->recall,2) }}</div></div>
<div class="eval-box"><div class="eval-label">F1 Score</div><div class="eval-val">{{ number_format($evaluasi->f1_score,2) }}</div></div>
<div class="eval-box"><div class="eval-label">Akurasi</div><div class="eval-val">{{ number_format($evaluasi->accuracy,2) }}</div></div>
</div>
@else
<p style="font-size:10px;color:#9ca3af;padding:20px 0">Data evaluasi belum tersedia.</p>
@endif
</div>
</div>
{{-- TABEL PER DESTINASI --}}
<div class="two-col no-break" style="margin-top:14px">
<div class="col-left">
<div class="section-title">🔭 TOTAL DATA PER DESTINASI</div>
<div class="table-wrap">
<table>
<thead><tr><th>No</th><th>Destinasi Wisata</th><th class="center">Total Ulasan</th></tr></thead>
<tbody>
@foreach($perWisata as $i => $w)
<tr><td>{{ $i+1 }}</td><td style="font-weight:bold">{{ $w->wisata }}</td><td class="center">{{ $w->total }}</td></tr>
@endforeach
</tbody>
<tfoot><tr><td colspan="2" style="font-weight:bold;padding:6px 10px">Total</td><td class="center" style="padding:6px 10px;font-weight:bold">{{ $totalUlasan }}</td></tr></tfoot>
</table>
</div>
</div>
<div class="col-right">
<div class="section-title">📉 SENTIMEN PER DESTINASI</div>
<div class="table-wrap">
<table>
<thead><tr><th>No</th><th>Destinasi Wisata</th><th class="center" style="color:#86efac">Positif</th><th class="center" style="color:#fca5a5">Negatif</th><th class="center" style="color:#fde68a">Netral</th></tr></thead>
<tbody>
@foreach($perWisata as $i => $w)
<tr>
<td>{{ $i+1 }}</td><td style="font-weight:bold">{{ $w->wisata }}</td>
<td class="center" style="color:#16a34a;font-weight:bold">{{ $w->positif }}</td>
<td class="center" style="color:#dc2626;font-weight:bold">{{ $w->negatif }}</td>
<td class="center" style="color:#d97706;font-weight:bold">{{ $w->netral }}</td>
</tr>
@endforeach
</tbody>
<tfoot><tr>
<td colspan="2" style="padding:6px 10px;font-weight:bold">Total</td>
<td class="center" style="padding:6px 10px;color:#16a34a;font-weight:bold">{{ $totalPositif }}</td>
<td class="center" style="padding:6px 10px;color:#dc2626;font-weight:bold">{{ $totalNegatif }}</td>
<td class="center" style="padding:6px 10px;color:#d97706;font-weight:bold">{{ $totalNetral }}</td>
</tr></tfoot>
</table>
</div>
</div>
</div>
{{-- DETAIL + REKOMENDASI --}}
<div class="two-col no-break" style="margin-top:14px">
<div class="col-left">
<div class="section-title">📋 DETAIL HASIL ANALISIS</div>
<div class="table-wrap">
<table>
<thead><tr><th width="6%">No</th><th width="22%">Destinasi</th><th width="44%">Ulasan</th><th width="16%">Sentimen</th><th width="12%" class="center">Prob.</th></tr></thead>
<tbody>
@foreach($hasilAnalisis->take(8) as $i => $item)
<tr>
<td>{{ $i+1 }}</td>
<td style="font-size:8.5px">{{ $item->wisata }}</td>
<td style="font-size:8.5px">{{ \Illuminate\Support\Str::limit($item->ulasan_asli ?? '-', 75) }}</td>
<td><span class="badge badge-{{ strtolower($item->sentimen) }}">{{ ucfirst($item->sentimen) }}</span></td>
<td class="center">{{ number_format($item->probabilitas,2) }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@if($hasilAnalisis->count() > 8)
<p style="font-size:8.5px;color:#3b82f6;margin-top:5px;text-align:right">* Lihat semua data ulasan pada sistem </p>
@endif
</div>
<div class="col-right">
<div class="section-title">💡 REKOMENDASI LAYANAN</div>
@php $iconColors = ['red','orange','green','blue','purple']; @endphp
@forelse($rekomendasi as $i => $r)
@php $c = $iconColors[$i % count($iconColors)]; @endphp
<div class="rekom-item">
<div class="rekom-icon ri-{{ $c }}">{{ $r['icon'] }}</div>
<div><div class="rtitle">{{ $r['nama'] }}</div><div class="rtip">{{ $r['tip'] }}</div></div>
</div>
@empty
<p style="font-size:10px;color:#9ca3af;padding:20px 0;text-align:center">Tidak ada keluhan signifikan. 🎉</p>
@endforelse
</div>
</div>
</div>
{{-- FOOTER --}}
<div class="metodologi">
<div class="metod-left">
<h4>METODOLOGI</h4>
<p>Analisis sentimen dilakukan menggunakan metode Naive Bayes Classifier pada data ulasan Google Maps yang telah melalui proses preprocessing (teks cleaning, case folding, tokenizing, filtering, stemming).</p>
</div>
<div class="metod-right">
<div class="mdate">Dicetak pada: {{ now()->translatedFormat('d F Y H:i') }} WIB</div>
<div class="msys">Sistem Analisis Sentimen Wisata</div>
<div style="font-size:8.5px;color:rgba(255,255,255,.6)">Kabupaten Jember</div>
</div>
</div>
{{-- HALAMAN 2: TABEL LENGKAP --}}
@if($hasilAnalisis->count() > 8)
<div class="page-break"></div>
<div class="page-body">
<div style="margin-bottom:12px">
<div style="font-size:14px;font-weight:bold;color:#1d4ed8">Detail Lengkap Hasil Analisis</div>
<div style="font-size:9px;color:#6b7280">Periode: {{ $periode->nama }} Total {{ $hasilAnalisis->count() }} data</div>
</div>
<div class="table-wrap">
<table>
<thead><tr><th width="5%">No</th><th width="20%">Destinasi</th><th width="55%">Ulasan Asli</th><th width="12%">Sentimen</th><th width="8%" class="center">Prob.</th></tr></thead>
<tbody>
@foreach($hasilAnalisis as $i => $item)
<tr>
<td>{{ $i+1 }}</td>
<td style="font-size:8.5px">{{ $item->wisata }}</td>
<td style="font-size:8.5px">{{ \Illuminate\Support\Str::limit($item->ulasan_asli ?? '-', 130) }}</td>
<td><span class="badge badge-{{ strtolower($item->sentimen) }}">{{ ucfirst($item->sentimen) }}</span></td>
<td class="center">{{ number_format($item->probabilitas,2) }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="metodologi" style="margin-top:16px">
<div class="metod-left"><h4>METODOLOGI</h4><p>Analisis sentimen menggunakan Naive Bayes Classifier dengan preprocessing teks.</p></div>
<div class="metod-right"><div class="mdate">{{ now()->translatedFormat('d F Y H:i') }} WIB</div><div class="msys">SENTARA Sistem Analisis Wisata Jember</div></div>
</div>
</div>
@endif
</body>
</html>