MIF_HomsinNIME31231582/resources/views/data-management.blade.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>