418 lines
24 KiB
PHP
418 lines
24 KiB
PHP
<x-app-layout>
|
|
<x-slot name="header">
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<h2 class="font-black text-2xl text-white flex items-center">
|
|
<i class="fas fa-database text-white/80 mr-3 text-3xl"></i>
|
|
Manajemen Data & Dataset
|
|
</h2>
|
|
<p class="text-sm text-white/70 mt-1">Kelola, upload, dan export dataset analisis sentimen</p>
|
|
</div>
|
|
<div class="flex space-x-2">
|
|
<span class="px-3 py-1 bg-white/20 text-white rounded-full text-xs font-bold flex items-center border border-white/10">
|
|
<i class="fas fa-database mr-1"></i>
|
|
Total: {{ number_format($totalData) }} data
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</x-slot>
|
|
|
|
<div class="py-8 bg-gray-50" x-data="{
|
|
showUploadModal: false,
|
|
showDeleteModal: false,
|
|
selectedFile: null,
|
|
uploading: false,
|
|
editModal: false,
|
|
editId: null,
|
|
editReview: '',
|
|
editScore: 0,
|
|
editSentiment: 'positif'
|
|
}">
|
|
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
|
|
|
|
<!-- Alert Messages -->
|
|
@if(session('success'))
|
|
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 rounded-lg shadow-md animate__animated animate__fadeInDown" role="alert">
|
|
<div class="flex items-center">
|
|
<i class="fas fa-check-circle text-green-500 mr-3 text-xl"></i>
|
|
<p class="font-bold">{{ session('success') }}</p>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
@if(session('error'))
|
|
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded-lg shadow-md animate__animated animate__fadeInDown" role="alert">
|
|
<div class="flex items-center">
|
|
<i class="fas fa-exclamation-circle text-red-500 mr-3 text-xl"></i>
|
|
<p class="font-bold">{{ session('error') }}</p>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
@if($errors->any())
|
|
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded-lg shadow-md">
|
|
<div class="flex items-start">
|
|
<i class="fas fa-exclamation-triangle text-red-500 mr-3 text-xl mt-1"></i>
|
|
<div>
|
|
<p class="font-bold">Terjadi kesalahan:</p>
|
|
<ul class="list-disc list-inside text-sm mt-1">
|
|
@foreach($errors->all() as $error)
|
|
<li>{{ $error }}</li>
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Quick Stats - Biru Navy -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<div class="bg-[#1e3a8a] rounded-xl shadow-lg p-6 border border-white/10">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm text-white/70">Total Dataset</p>
|
|
<p class="text-2xl font-black text-white">{{ number_format($totalData) }}</p>
|
|
</div>
|
|
<div class="w-12 h-12 bg-white/10 rounded-full flex items-center justify-center">
|
|
<i class="fas fa-database text-white text-xl"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-[#1e3a8a] rounded-xl shadow-lg p-6 border border-white/10">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm text-white/70">Data Positif</p>
|
|
<p class="text-2xl font-black text-green-400">{{ number_format($positif) }}</p>
|
|
</div>
|
|
<div class="w-12 h-12 bg-white/10 rounded-full flex items-center justify-center">
|
|
<i class="fas fa-smile text-green-400 text-xl"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-[#1e3a8a] rounded-xl shadow-lg p-6 border border-white/10">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm text-white/70">Data Negatif</p>
|
|
<p class="text-2xl font-black text-red-400">{{ number_format($negatif) }}</p>
|
|
</div>
|
|
<div class="w-12 h-12 bg-white/10 rounded-full flex items-center justify-center">
|
|
<i class="fas fa-frown text-red-400 text-xl"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-[#1e3a8a] rounded-xl shadow-lg p-6 border border-white/10">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm text-white/70">Akurasi Model</p>
|
|
<p class="text-2xl font-black text-blue-400">{{ number_format(($metrics['accuracy'] ?? 0) * 100, 1) }}%</p>
|
|
</div>
|
|
<div class="w-12 h-12 bg-white/10 rounded-full flex items-center justify-center">
|
|
<i class="fas fa-chart-line text-blue-400 text-xl"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<!-- Upload Card -->
|
|
<div class="bg-[#1e3a8a] rounded-2xl shadow-lg overflow-hidden">
|
|
<div class="bg-white/10 px-6 py-4 border-b border-white/10">
|
|
<h3 class="text-white font-bold flex items-center">
|
|
<i class="fas fa-upload mr-2 text-white/80"></i>
|
|
Upload Dataset
|
|
</h3>
|
|
</div>
|
|
<div class="p-6">
|
|
<form action="{{ route('data.upload') }}" method="POST" enctype="multipart/form-data" class="space-y-4">
|
|
@csrf
|
|
<div class="border-2 border-dashed border-white/20 rounded-xl p-6 text-center hover:border-white/40 transition bg-white/5"
|
|
@dragover.prevent="active = true"
|
|
@dragleave.prevent="active = false"
|
|
@drop.prevent="active = false; $refs.file.files = $event.dataTransfer.files">
|
|
|
|
<i class="fas fa-cloud-upload-alt text-4xl text-white/40 mb-3"></i>
|
|
<p class="text-sm text-white/70 mb-2">
|
|
Drag & drop file CSV disini, atau
|
|
</p>
|
|
<label class="cursor-pointer">
|
|
<span class="bg-white/20 hover:bg-white/30 text-white px-4 py-2 rounded-lg font-bold text-sm inline-block transition border border-white/10">
|
|
<i class="fas fa-folder-open mr-2"></i>Browse File
|
|
</span>
|
|
<input type="file" name="dataset" accept=".csv,.txt" class="hidden" required x-ref="file" @change="selectedFile = $event.target.files[0]">
|
|
</label>
|
|
<p class="text-xs text-white/50 mt-3">
|
|
Format: CSV dengan kolom: review, text_final, score, sentiment
|
|
</p>
|
|
|
|
<template x-if="selectedFile">
|
|
<div class="mt-4 p-3 bg-green-500/20 rounded-lg border border-green-500/30">
|
|
<i class="fas fa-check-circle text-green-400 mr-2"></i>
|
|
<span class="text-sm text-green-300" x-text="selectedFile.name"></span>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between">
|
|
<label class="flex items-center space-x-2 text-sm text-white/70">
|
|
<input type="checkbox" name="replace_data" value="1" class="rounded bg-white/20 border-white/30 text-blue-600">
|
|
<span>Ganti semua data yang ada</span>
|
|
</label>
|
|
<button type="submit" class="bg-white/20 hover:bg-white/30 text-white px-6 py-2 rounded-lg font-bold transition transform hover:scale-105 border border-white/10">
|
|
<i class="fas fa-upload mr-2"></i>Upload
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Export & Delete Card -->
|
|
<div class="bg-[#1e3a8a] rounded-2xl shadow-lg overflow-hidden">
|
|
<div class="bg-white/10 px-6 py-4 border-b border-white/10">
|
|
<h3 class="text-white font-bold flex items-center">
|
|
<i class="fas fa-download mr-2 text-white/80"></i>
|
|
Export & Maintenance
|
|
</h3>
|
|
</div>
|
|
<div class="p-6">
|
|
<div class="grid grid-cols-2 gap-4 mb-6">
|
|
<a href="{{ route('export.pdf') }}" class="bg-red-500/20 hover:bg-red-500/30 text-white p-4 rounded-xl transition transform hover:scale-105 text-center border border-red-500/30">
|
|
<i class="fas fa-file-pdf text-2xl mb-2 text-red-400"></i>
|
|
<p class="font-bold text-sm">Export PDF</p>
|
|
</a>
|
|
<a href="{{ route('export.excel') }}" class="bg-green-500/20 hover:bg-green-500/30 text-white p-4 rounded-xl transition transform hover:scale-105 text-center border border-green-500/30">
|
|
<i class="fas fa-file-excel text-2xl mb-2 text-green-400"></i>
|
|
<p class="font-bold text-sm">Export Excel</p>
|
|
</a>
|
|
</div>
|
|
|
|
<form action="{{ route('data.truncate') }}" method="POST" onsubmit="return confirm('⚠️ PERHATIAN! Anda akan menghapus SEMUA data. Tindakan ini tidak dapat dibatalkan. Lanjutkan?');">
|
|
@csrf
|
|
@method('POST')
|
|
<button type="submit" class="w-full bg-red-500/20 hover:bg-red-500/30 text-white px-4 py-3 rounded-xl font-bold transition transform hover:scale-105 flex items-center justify-center space-x-2 border border-red-500/30">
|
|
<i class="fas fa-trash-alt text-red-400"></i>
|
|
<span>Hapus Semua Data</span>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filter & Search -->
|
|
<div class="bg-[#1e3a8a] rounded-xl shadow-lg p-6">
|
|
<form method="GET" action="{{ route('data.management') }}" class="grid grid-cols-1 md:grid-cols-5 gap-4">
|
|
<div class="col-span-2">
|
|
<label class="block text-sm font-bold text-white/80 mb-2">
|
|
<i class="fas fa-search mr-1 text-white/60"></i>Cari Review
|
|
</label>
|
|
<div class="relative">
|
|
<input type="text" name="search" value="{{ $search ?? '' }}" placeholder="Kata kunci review..."
|
|
class="w-full rounded-xl bg-white/10 border-white/20 pl-10 text-white placeholder-white/50 focus:border-white/30 focus:ring-white/30">
|
|
<i class="fas fa-search absolute left-3 top-3 text-white/40"></i>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-bold text-white/80 mb-2">
|
|
<i class="fas fa-filter mr-1 text-white/60"></i>Filter Sentimen
|
|
</label>
|
|
<select name="filter_sentiment" class="w-full rounded-xl bg-white/10 border-white/20 text-white focus:border-white/30 focus:ring-white/30">
|
|
<option value="" class="bg-[#1e3a8a]">Semua</option>
|
|
<option value="positif" {{ ($filter_sentiment ?? '') == 'positif' ? 'selected' : '' }} class="bg-[#1e3a8a]">Positif</option>
|
|
<option value="negatif" {{ ($filter_sentiment ?? '') == 'negatif' ? 'selected' : '' }} class="bg-[#1e3a8a]">Negatif</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-bold text-white/80 mb-2">
|
|
<i class="fas fa-list mr-1 text-white/60"></i>Tampilkan
|
|
</label>
|
|
<select name="per_page" class="w-full rounded-xl bg-white/10 border-white/20 text-white focus:border-white/30 focus:ring-white/30">
|
|
<option value="10" {{ ($per_page ?? 10) == 10 ? 'selected' : '' }} class="bg-[#1e3a8a]">10 per halaman</option>
|
|
<option value="25" {{ ($per_page ?? 10) == 25 ? 'selected' : '' }} class="bg-[#1e3a8a]">25 per halaman</option>
|
|
<option value="50" {{ ($per_page ?? 10) == 50 ? 'selected' : '' }} class="bg-[#1e3a8a]">50 per halaman</option>
|
|
<option value="100" {{ ($per_page ?? 10) == 100 ? 'selected' : '' }} class="bg-[#1e3a8a]">100 per halaman</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="flex items-end space-x-2">
|
|
<button type="submit" class="flex-1 bg-white/20 hover:bg-white/30 text-white px-4 py-2.5 rounded-xl font-bold transition transform hover:scale-105 border border-white/10">
|
|
<i class="fas fa-search mr-2"></i>Filter
|
|
</button>
|
|
<a href="{{ route('data.management') }}" class="flex-1 bg-white/10 hover:bg-white/20 text-white px-4 py-2.5 rounded-xl font-bold transition transform hover:scale-105 text-center border border-white/10">
|
|
<i class="fas fa-redo-alt mr-2"></i>Reset
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Data Table -->
|
|
<div class="bg-[#1e3a8a] rounded-2xl shadow-lg overflow-hidden">
|
|
<div class="bg-white/10 px-6 py-4 flex justify-between items-center border-b border-white/10">
|
|
<h3 class="text-white font-bold flex items-center">
|
|
<i class="fas fa-table mr-2 text-white/80"></i>
|
|
Tabel Dataset
|
|
</h3>
|
|
<span class="text-white text-sm bg-white/20 px-3 py-1 rounded-full border border-white/10">
|
|
{{ $reviews->firstItem() ?? 0 }} - {{ $reviews->lastItem() ?? 0 }} dari {{ $reviews->total() }} data
|
|
</span>
|
|
</div>
|
|
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-sm text-left">
|
|
<thead class="bg-white/5 text-xs font-bold text-white/80 uppercase">
|
|
<tr>
|
|
<th class="px-6 py-4">ID</th>
|
|
<th class="px-6 py-4">Review</th>
|
|
<th class="px-6 py-4">Text Final</th>
|
|
<th class="px-6 py-4">Score</th>
|
|
<th class="px-6 py-4">Sentimen</th>
|
|
<th class="px-6 py-4">Tanggal</th>
|
|
<th class="px-6 py-4">Aksi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-white/10">
|
|
@forelse($reviews ?? [] as $review)
|
|
<tr class="hover:bg-white/5 transition">
|
|
<td class="px-6 py-4 font-mono text-sm text-white/70">#{{ $review->id ?? '-' }}</td>
|
|
|
|
<td class="px-6 py-4 max-w-xs">
|
|
<div class="flex items-center text-white">
|
|
<i class="fas fa-quote-right text-white/30 mr-2 text-xs"></i>
|
|
<span title="{{ $review->review ?? '' }}" class="truncate block">
|
|
{{ Str::limit($review->review ?? '-', 50) }}
|
|
</span>
|
|
</div>
|
|
</td>
|
|
|
|
<td class="px-6 py-4 max-w-xs text-white/60">
|
|
<span title="{{ $review->text_final ?? $review->review ?? '' }}" class="truncate block">
|
|
{{ Str::limit($review->text_final ?? $review->review ?? '-', 40) }}
|
|
</span>
|
|
</td>
|
|
|
|
<td class="px-6 py-4">
|
|
@php $score = $review->score ?? 0; @endphp
|
|
<span class="px-3 py-1 rounded-full text-xs font-bold
|
|
{{ $score >= 60 ? 'bg-green-500/20 text-green-300' : ($score >= 40 ? 'bg-yellow-500/20 text-yellow-300' : 'bg-red-500/20 text-red-300') }}">
|
|
{{ $score }}
|
|
</span>
|
|
</td>
|
|
|
|
<td class="px-6 py-4">
|
|
@php $sentiment = $review->sentiment ?? 'netral'; @endphp
|
|
<span class="px-3 py-1 rounded-full text-xs font-bold flex items-center w-fit
|
|
{{ $sentiment == 'positif' ? 'bg-green-500/20 text-green-300' : ($sentiment == 'negatif' ? 'bg-red-500/20 text-red-300' : 'bg-gray-500/20 text-gray-300') }}">
|
|
<i class="fas fa-{{ $sentiment == 'positif' ? 'smile' : ($sentiment == 'negatif' ? 'frown' : 'meh') }} mr-1"></i>
|
|
{{ ucfirst($sentiment) }}
|
|
</span>
|
|
</td>
|
|
|
|
<td class="px-6 py-4 text-sm text-white/50">
|
|
<i class="far fa-calendar-alt mr-1"></i>
|
|
{{ $review->created_at ? $review->created_at->format('d/m/Y H:i') : date('d/m/Y H:i') }}
|
|
</td>
|
|
|
|
<td class="px-6 py-4">
|
|
<div class="flex space-x-2">
|
|
<button onclick='editReview({{ $review->id ?? 0 }}, @json($review->review ?? ""), {{ $review->score ?? 0 }}, @json($review->sentiment ?? "netral"))'
|
|
class="p-2 text-blue-400 hover:bg-white/10 rounded-lg transition" title="Edit">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button onclick="deleteReview({{ $review->id ?? 0 }})"
|
|
class="p-2 text-red-400 hover:bg-white/10 rounded-lg transition" title="Delete">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="7" class="px-6 py-12 text-center text-white/50">
|
|
<i class="fas fa-database text-4xl mb-3 opacity-50"></i>
|
|
<p class="text-lg font-bold">Belum ada data tersedia</p>
|
|
<p class="text-sm mt-2">Silakan upload file CSV untuk memulai</p>
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
@if(isset($reviews) && method_exists($reviews, 'links'))
|
|
<div class="px-6 py-4 border-t border-white/10">
|
|
{{ $reviews->appends(request()->query())->links() }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
<!-- Info Card -->
|
|
<div class="bg-white/10 backdrop-blur-sm rounded-xl p-4 border border-white/10">
|
|
<div class="flex items-start space-x-3">
|
|
<i class="fas fa-info-circle text-white/60 text-xl mt-1"></i>
|
|
<div>
|
|
<p class="text-sm font-bold text-white">Informasi Dataset</p>
|
|
<p class="text-xs text-white/60 mt-1">
|
|
Dataset terdiri dari {{ number_format($totalData) }} review dengan {{ number_format($positif) }} data positif ({{ number_format(($positif/$totalSafe)*100, 1) }}%) dan {{ number_format($negatif) }} data negatif ({{ number_format(($negatif/$totalSafe)*100, 1) }}%).
|
|
Format file CSV yang diterima: review, steming_data, score (integer -9 s/d 9), sentiment (positif/negatif).
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@push('scripts')
|
|
<script>
|
|
// Format angka
|
|
function formatNumber(num) {
|
|
return new Intl.NumberFormat('id-ID').format(num);
|
|
}
|
|
|
|
// Auto hide alert messages
|
|
setTimeout(() => {
|
|
document.querySelectorAll('[role="alert"]').forEach(el => {
|
|
el.style.transition = 'opacity 0.5s';
|
|
el.style.opacity = '0';
|
|
setTimeout(() => el.remove(), 500);
|
|
});
|
|
}, 5000);
|
|
|
|
// Fungsi Edit Review
|
|
function editReview(id, review, score, sentiment) {
|
|
if(confirm('Edit review?\nFitur ini akan segera tersedia')) {
|
|
// Implementasi edit akan ditambahkan kemudian
|
|
console.log('Edit review:', id, review, score, sentiment);
|
|
}
|
|
}
|
|
|
|
// Fungsi Delete Review
|
|
function deleteReview(id) {
|
|
if(confirm('Apakah Anda yakin ingin menghapus data ini?')) {
|
|
fetch(`/data/review/${id}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'X-CSRF-TOKEN': '{{ csrf_token() }}',
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if(data.success) {
|
|
location.reload();
|
|
} else {
|
|
alert('Gagal menghapus data: ' + data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
alert('Terjadi kesalahan: ' + error);
|
|
});
|
|
}
|
|
}
|
|
</script>
|
|
@endpush
|
|
</x-app-layout> |