310 lines
11 KiB
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>
|