MIF_E31222544/frontend/src/views/ScraperView.vue

310 lines
11 KiB
Vue

<template>
<div class="fixed inset-0 w-full h-full flex bg-update-bg bg-cover bg-center">
<div class="absolute left-5 top-5 h-1/4 bg-gray-700 bg-opacity-90 border-r-4 border-gray-500 pixel-border shadow-lg w-16 flex flex-col items-center p-4">
<nav class="flex flex-col space-y-6 text-center mt-4">
<a href="/dashboard" class="text-white hover:text-yellow-400 transition">
<LayoutDashboard class="w-8 h-8" />
</a>
<a href="/scraper" class="text-white hover:text-blue-400 transition">
<Bot class="w-8 h-8" />
</a>
</nav>
</div>
<div class="flex flex-col w-full max-w-full gap-6 ml-10 p-4">
<!-- Scraping Section -->
<div class="w-full bg-gray-700 bg-opacity-90 border-4 border-gray-500 pixel-border flex flex-col items-center p-6">
<h2 class="text-center text-xl font-bold font-pixel text-white mb-4"></h2>
<div class="flex flex-col md:flex-row gap-4">
<button @click="startScrapeScopus" class="bg-blue-600 hover:bg-blue-700 text-white font-pixel px-4 py-2 rounded shadow">
Scrape Scopus
</button>
<button @click="startScrapeHki" class="bg-green-600 hover:bg-green-700 text-white font-pixel px-4 py-2 rounded shadow">
Scrape HKI
</button>
<button @click="startScrapeGoogleScholar" class="bg-purple-600 hover:bg-purple-700 text-white font-pixel px-4 py-2 rounded shadow">
Scrape Google Scholar
</button>
<button @click="startScrapePenelitian" class="bg-red-600 hover:bg-red-700 text-white font-pixel px-4 py-2 rounded shadow">
Scrape Penelitian
</button>
<button @click="startScrapePengabdian" class="bg-yellow-600 hover:bg-yellow-700 text-white font-pixel px-4 py-2 rounded shadow">
Scrape Pengabdian
</button>
<!-- <a v-if="latestFile" :href="`http://localhost:8000/scopus/download`" target="_blank" class="bg-green-600 hover:bg-green-700 text-white font-pixel px-4 py-2 rounded shadow">
Download Data
</a>
<router-link v-if="latestFile" to="/data-cleaning" class="bg-purple-600 hover:bg-purple-700 text-white font-pixel px-4 py-2 rounded shadow">
Open Cleaning
</router-link> -->
</div>
<div class="mt-6 text-sm font-pixel">
<div v-if="loading" class="text-yellow-300 animate-pulse">🔄 Sedang memproses ...</div>
<div v-if="statusMessage" class="text-green-400">{{ statusMessage }}</div>
<div v-if="errorMessage" class="text-red-400">{{ errorMessage }}</div>
</div>
</div>
<div
class="w-full p-6 bg-gray-800 border-4 border-dashed border-gray-600 rounded-lg text-center text-white font-pixel"
@dragover.prevent
@drop.prevent="handleDrop"
>
<p>📂 Tarik dan lepas file Excel di sini atau klik untuk upload</p>
<input type="file" accept=".xlsx" class="hidden" ref="fileInput" @change="handleFileUpload" />
<button @click="$refs.fileInput.click()" class="mt-4 bg-blue-700 hover:bg-blue-800 px-4 py-2 rounded">
Upload Manual
</button>
</div>
<!-- Modal Konfirmasi -->
<div v-if="showModal" class="fixed inset-0 bg-black bg-opacity-60 flex justify-center items-center z-50">
<div class="bg-gray-700 rounded-lg shadow-lg w-96 p-6">
<h3 class="text-lg font-bold mb-4">Pilih Kategori Grafik</h3>
<p class="mb-4 text-sm text-gray-700">File berhasil dibersihkan. Masukkan sebagai grafik apa?</p>
<div class="grid grid-cols-2 gap-4 mb-6">
<button v-for="option in chartOptions" :key="option" @click="submitCleanedFile(option)"
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded font-pixel text-sm">
{{ option }}
</button>
</div>
<button @click="showModal = false" class="text-gray-500 hover:text-red-500 text-sm">Batal</button>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
import { LayoutDashboard, Bot, Settings, LogOut } from "lucide-vue-next"
const loading = ref(false)
const statusMessage = ref("")
const errorMessage = ref("")
const latestFile = ref("")
const tableData = ref([])
const showModal = ref(false)
const cleanedFile = ref(null)
const chartOptions = ["HKI", "Penelitian", "Pengabdian", "Scopus", "Scholar"]
const selectChart = (option) => {
selectedChart.value = option
showModal.value = false
// Simpan info chart untuk dashboard (bisa via localStorage, Vuex, atau query parameter)
localStorage.setItem("uploadedChartTarget", option)
alert(`✅ File akan dimasukkan ke grafik: ${option}`) // Ganti dengan router push jika ingin langsung ke dashboard
// Optionally, arahkan ke DashboardView.vue
// router.push('/dashboard')
}
const triggerDownload = (filename, source) => {
const link = document.createElement('a')
link.href = `http://localhost:8000/${source}/download`
link.download = filename
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
const startScrapeScopus = async () => {
loading.value = true
statusMessage.value = ""
errorMessage.value = ""
try {
await axios.get(`http://localhost:8000/scrape/scopus`)
statusMessage.value = "✅ Scraping selesai!"
await fetchLatestData()
if (latestFile.value) {
triggerDownload(latestFile.value)
}
} catch (err) {
errorMessage.value = "❌ Gagal scraping: " + err.message
} finally {
loading.value = false
}
}
const startScrapeGoogleScholar = async () => {
loading.value = true
statusMessage.value = ""
errorMessage.value = ""
try {
const response = await axios.get(`http://localhost:8000/scrape/scholar`)
console.log(response.data)
statusMessage.value = response.data.message || "✅ Scraping Scholar selesai!"
await fetchLatestData()
if (latestFile.value) {
triggerDownload(latestFile.value)
}
} catch (err) {
console.error(err)
errorMessage.value = "❌ Gagal scraping Scholar: " + (err.response?.data?.error || err.message)
} finally {
loading.value = false
}
}
const startScrapeHki = async () => {
loading.value = true
statusMessage.value = ""
errorMessage.value = ""
try {
await axios.get(`http://localhost:8000/scrape/hki`)
statusMessage.value = "✅ Scraping HKI selesai!"
await fetchLatestData()
if (latestFile.value) {
triggerDownload(latestFile.value)
}
} catch (err) {
errorMessage.value = "❌ Gagal scraping HKI: " + err.message
} finally {
loading.value = false
}
}
const startScrapePenelitian = async () => {
loading.value = true
statusMessage.value = ""
errorMessage.value = ""
try {
await axios.get(`http://localhost:8000/scrape/penelitian`)
statusMessage.value = "✅ Scraping Penelitian selesai!"
await fetchLatestData()
if (latestFile.value) {
triggerDownload(latestFile.value)
}
} catch (err) {
errorMessage.value = "❌ Gagal scraping Penelitian: " + err.message
} finally {
loading.value = false
}
}
const startScrapePengabdian = async () => {
loading.value = true
statusMessage.value = ""
errorMessage.value = ""
try {
await axios.get(`http://localhost:8000/scrape/pengabdian`) // Ganti dengan endpoint scraping Pengabdian
statusMessage.value = "✅ Scraping Pengabdian selesai!"
await fetchLatestData('pengabdian')
if (latestFile.value) {
triggerDownload(latestFile.value)
}
} catch (err) {
errorMessage.value = "❌ Gagal scraping Pengabdian: " + err.message
} finally {
loading.value = false
}
}
const fetchLatestData = async () => {
try {
const resFile = await axios.get(`http://localhost:8000/scopus/latest-file`)
latestFile.value = resFile.data.file
if (latestFile.value) {
const resData = await axios.get(`http://localhost:8000/scopus/preview`)
tableData.value = resData.data.data
}
} catch (err) {
console.error(err)
}
}
const handleDrop = (e) => {
const file = e.dataTransfer.files[0]
if (file && file.name.endsWith('.xlsx')) {
uploadExcel(file)
} else {
errorMessage.value = "❌ File bukan format .xlsx"
}
}
const handleFileUpload = (e) => {
const file = e.target.files[0]
if (file && file.name.endsWith('.xlsx')) {
uploadExcel(file)
} else {
errorMessage.value = "❌ File bukan format .xlsx"
}
}
const startScrapeAndDownload = async (type) => {
const response = await fetch(`http://localhost:8000/scrape-download/${type}`);
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `${type}_data.xlsx`;
a.click();
window.URL.revokeObjectURL(url);
} else {
alert(`Gagal scraping ${type}`);
}
}
const uploadExcel = async (file) => {
const formData = new FormData();
formData.append("file", file);
try {
loading.value = true;
const response = await axios.post("http://localhost:8000/upload-excel", formData, { responseType: 'blob' });
// Simpan file di blob untuk diproses lagi setelah memilih grafik
cleanedFile.value = new Blob([response.data], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
showModal.value = true; // Tampilkan modal
statusMessage.value = "✅ File berhasil dibersihkan. Pilih grafik tujuan.";
} catch (err) {
console.error("Error response:", err.response);
errorMessage.value = "❌ Upload gagal: " + (err.response?.data?.error || err.message);
} finally {
loading.value = false;
}
}
const submitCleanedFile = async (chartTypeRaw) => {
const chartType = chartTypeRaw.toLowerCase()
if (!cleanedFile.value) return;
const formData = new FormData();
formData.append("file", cleanedFile.value, `${chartType}_cleaned.xlsx`);
try {
const response = await axios.post(`http://localhost:8000/submit-cleaned/${chartType}`, formData);
statusMessage.value = `✅ File berhasil dikirim ke grafik ${chartTypeRaw}`;
showModal.value = false;
cleanedFile.value = null;
} catch (err) {
console.error(err);
errorMessage.value = `❌ Gagal mengirim ke grafik ${chartTypeRaw}: ` + (err.response?.data?.error || err.message);
}
};
onMounted(fetchLatestData)
</script>
<style scoped>
.font-pixel {
font-family: 'Press Start 2P', cursive;
}
.pixel-border {
image-rendering: pixelated;
position: relative;
border-width: 4px;
border-style: solid;
border-radius: 8px;
box-shadow:
inset -2px -2px 0px rgba(0, 0, 0, 0.8),
inset 2px 2px 0px rgba(255, 255, 255, 0.2);
}
</style>