671 lines
23 KiB
Vue
671 lines
23 KiB
Vue
<template>
|
|
<div class="fixed inset-0 w-full h-full flex bg-update-bg bg-cover bg-center">
|
|
<!-- Sidebar -->
|
|
<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>
|
|
|
|
<!-- Main Content -->
|
|
<div class="flex-1 ml-20 overflow-y-auto p-4">
|
|
<Modal v-if="isModalOpen" @close="isModalOpen = false">
|
|
<template #header>
|
|
<h3 class="text-xl font-bold text-white">Edit Data {{ modalTitle }}</h3>
|
|
</template>
|
|
<template #body>
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full text-sm text-white table-auto">
|
|
<thead>
|
|
<tr>
|
|
<th v-for="(header, index) in tableHeaders" :key="index" class="px-4 py-2 border-b border-gray-500">{{ header }}</th>
|
|
<th class="px-4 py-2 border-b border-gray-500">Aksi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(row, rowIndex) in editableData" :key="rowIndex">
|
|
<td v-for="(header, colIndex) in tableHeaders" :key="colIndex" class="px-4 py-2 border-b border-gray-600">
|
|
<input v-model="editableData[rowIndex][header]" class="bg-gray-700 text-white px-2 py-1 w-full rounded" />
|
|
</td>
|
|
<td class="px-4 py-2 border-b border-gray-600 text-center">
|
|
<button @click="removeRow(rowIndex)" class="text-red-500 hover:underline">Hapus</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<button @click="addRow" class="mt-4 bg-green-500 hover:bg-green-400 text-white px-4 py-2 rounded">Tambah Baris</button>
|
|
</div>
|
|
</template>
|
|
<template #footer>
|
|
<button @click="saveChanges" class="bg-blue-600 hover:bg-blue-500 text-white px-4 py-2 rounded">Simpan Perubahan</button>
|
|
</template>
|
|
</Modal>
|
|
<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-white text-xl font-bold mb-6">DASHBOARD</h2>
|
|
<div class="flex items-center gap-4 mb-10">
|
|
<button @click="refreshData" class="text-white bg-green-600 hover:bg-green-500 px-4 py-2 rounded-2xl">
|
|
Refresh Grafik
|
|
</button>
|
|
<button @click="clearCache" class="text-white bg-red-600 hover:bg-red-500 px-4 py-2 rounded-2xl">
|
|
Reset
|
|
</button>
|
|
</div>
|
|
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 w-full">
|
|
|
|
<!-- Pengabdian -->
|
|
<div class="bg-gray-800 p-4 rounded-lg text-white w-full h-96 flex flex-col justify-between">
|
|
<h3 class="text-lg font-semibold mb-2">Pengabdian</h3>
|
|
<div class="relative flex justify-end gap-2">
|
|
<a
|
|
:href="`http://localhost:8000/download/pengabdian`"
|
|
class="text-xs bg-green-600 hover:bg-green-500 text-white px-2 py-1 rounded"
|
|
download
|
|
>
|
|
⬇ Excel
|
|
</a>
|
|
<button
|
|
@click="refreshSingleData('pengabdian')"
|
|
class="text-xs bg-blue-600 hover:bg-blue-500 text-white px-2 py-1 rounded"
|
|
title="Reload Grafik"
|
|
>
|
|
🔄
|
|
</button>
|
|
</div>
|
|
<button @click="openModal('pengabdian')" class="text-sm text-yellow-400 hover:underline mb-10">Edit</button>
|
|
<div
|
|
@dragover.prevent
|
|
@drop.prevent="handleDrop"
|
|
class="border-2 border-dashed border-gray-400 p-4 rounded-lg text-center cursor-pointer h-32 flex items-center justify-center hover:border-yellow-500"
|
|
>
|
|
<span class="text-sm text-gray-300">Tarik file Excel ke sini (drag & drop)</span>
|
|
</div>
|
|
<div v-if="pengabdian.length > 0" class="mt-4">
|
|
<BarChart :data="pengabdianChartData" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- HKI -->
|
|
<div class="bg-gray-800 p-4 rounded-lg text-white w-full h-96 flex flex-col justify-between">
|
|
<h3 class="text-lg font-semibold mb-2">HKI</h3>
|
|
<div class="relative flex justify-end gap-2">
|
|
<a
|
|
:href="`http://localhost:8000/download/hki`"
|
|
class="text-xs bg-green-600 hover:bg-green-500 text-white px-2 py-1 rounded"
|
|
download
|
|
>
|
|
⬇ Excel
|
|
</a>
|
|
<button
|
|
@click="refreshSingleData('hki')"
|
|
class="text-xs bg-blue-600 hover:bg-blue-500 text-white px-2 py-1 rounded"
|
|
title="Reload Grafik"
|
|
>
|
|
🔄
|
|
</button>
|
|
</div>
|
|
<button @click="openModal('hki')" class="text-sm text-yellow-400 hover:underline mb-10">Edit</button>
|
|
<div
|
|
@dragover.prevent
|
|
@drop.prevent="handleDropHKI"
|
|
class="border-2 border-dashed border-gray-400 p-4 rounded-lg text-center cursor-pointer h-32 flex items-center justify-center hover:border-yellow-500"
|
|
>
|
|
<span class="text-sm text-gray-300">Tarik file Excel ke sini (drag & drop)</span>
|
|
</div>
|
|
<div v-if="hki.length > 0" class="mt-4">
|
|
<BarChart :data="hkiChartData" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Penelitian -->
|
|
<div class="bg-gray-800 p-4 rounded-lg text-white w-full h-96 flex flex-col justify-between">
|
|
<h3 class="text-lg font-semibold mb-2">Penelitian</h3>
|
|
<div class="relative flex justify-end gap-2">
|
|
<a
|
|
:href="`http://localhost:8000/download/penelitian`"
|
|
class="text-xs bg-green-600 hover:bg-green-500 text-white px-2 py-1 rounded"
|
|
download
|
|
>
|
|
⬇ Excel
|
|
</a>
|
|
<button
|
|
@click="refreshSingleData('penelitian')"
|
|
class="text-xs bg-blue-600 hover:bg-blue-500 text-white px-2 py-1 rounded"
|
|
title="Reload Grafik"
|
|
>
|
|
🔄
|
|
</button>
|
|
</div>
|
|
<button @click="openModal('penelitian')" class="text-sm text-yellow-400 hover:underline mb-10">Edit</button>
|
|
<div
|
|
@dragover.prevent
|
|
@drop.prevent="handleDropPenelitian"
|
|
class="border-2 border-dashed border-gray-400 p-4 rounded-lg text-center cursor-pointer h-32 flex items-center justify-center hover:border-yellow-500"
|
|
>
|
|
<span class="text-sm text-gray-300">Tarik file Excel ke sini (drag & drop)</span>
|
|
</div>
|
|
<div v-if="penelitian.length > 0" class="mt-4">
|
|
<BarChart :data="penelitianChartData" />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Scopus -->
|
|
<div class="bg-gray-800 p-4 rounded-lg text-white w-full h-96 flex flex-col justify-between mt-6">
|
|
<h3 class="text-lg font-semibold mb-2">Scopus</h3>
|
|
<div class="relative flex justify-end gap-2">
|
|
<a
|
|
:href="`http://localhost:8000/download/scopus`"
|
|
class="text-xs bg-green-600 hover:bg-green-500 text-white px-2 py-1 rounded"
|
|
download
|
|
>
|
|
⬇ Excel
|
|
</a>
|
|
<button
|
|
@click="refreshSingleData('scopus')"
|
|
class="text-xs bg-blue-600 hover:bg-blue-500 text-white px-2 py-1 rounded"
|
|
title="Reload Grafik"
|
|
>
|
|
🔄
|
|
</button>
|
|
</div>
|
|
<button @click="openModal('scopus')" class="text-sm text-yellow-400 hover:underline mb-10">Edit</button>
|
|
<div
|
|
@dragover.prevent
|
|
@drop.prevent="handleDropScopus"
|
|
class="border-2 border-dashed border-gray-400 p-4 rounded-lg text-center cursor-pointer h-32 flex items-center justify-center hover:border-yellow-500"
|
|
>
|
|
<span class="text-sm text-gray-300">Tarik file Excel ke sini (drag & drop)</span>
|
|
</div>
|
|
<div v-if="scopus.length > 0" class="mt-4">
|
|
<BarChart :data="scopusChartData" />
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-800 p-4 rounded-lg text-white w-full h-96 flex flex-col justify-between mt-6">
|
|
<h3 class="text-lg font-semibold mb-2">Scholar</h3>
|
|
<div class="relative flex justify-end gap-2">
|
|
<a
|
|
:href="`http://localhost:8000/download/scholar`"
|
|
class="text-xs bg-green-600 hover:bg-green-500 text-white px-2 py-1 rounded"
|
|
download
|
|
>
|
|
⬇ Excel
|
|
</a>
|
|
<button
|
|
@click="refreshSingleData('scholar')"
|
|
class="text-xs bg-blue-600 hover:bg-blue-500 text-white px-2 py-1 rounded"
|
|
title="Reload Grafik"
|
|
>
|
|
🔄
|
|
</button>
|
|
</div>
|
|
<button @click="openModal('scholar')" class="text-sm text-yellow-400 hover:underline mb-10">Edit</button>
|
|
<div
|
|
@dragover.prevent
|
|
@drop.prevent="handleDropScholar"
|
|
class="border-2 border-dashed border-gray-400 p-4 rounded-lg text-center cursor-pointer h-32 flex items-center justify-center hover:border-yellow-500"
|
|
>
|
|
<span class="text-sm text-gray-300">Tarik file Excel ke sini (drag & drop)</span>
|
|
</div>
|
|
<div v-if="scholar.length > 0" class="mt-4">
|
|
<BarChart :data="scholarChartData" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-gray-800 p-4 rounded-lg text-white w-full h-[500px] col-span-1 md:col-span-2 mt-10">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold">Kontribusi Luaran Publikasi Dosen per Tahun</h3>
|
|
<select v-model="selectedYear" class="bg-gray-700 text-white p-2 rounded">
|
|
<option v-for="year in availableYears" :key="year" :value="year">{{ year }}</option>
|
|
</select>
|
|
</div>
|
|
<div class="w-80 h-80 mx-auto">
|
|
<canvas id="luaranPieChart" class="w-80 h-80" width="320" height="320"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="w-full bg-gray-700 bg-opacity-90 border-4 border-gray-500 pixel-border flex flex-col items-center p-6">
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import Modal from '@/components/Modal.vue'
|
|
import axios from 'axios'
|
|
import { LayoutDashboard, Bot, Settings, LogOut } from "lucide-vue-next"
|
|
import { computed, defineComponent, h } from 'vue'
|
|
import { ref } from 'vue'
|
|
import * as XLSX from 'xlsx'
|
|
import { Bar } from 'vue-chartjs'
|
|
import {
|
|
Chart as ChartJS,
|
|
Title, Tooltip, Legend,
|
|
BarElement, CategoryScale, LinearScale
|
|
} from 'chart.js'
|
|
import { onMounted, watch} from 'vue'
|
|
import Chart from 'chart.js/auto';
|
|
|
|
onMounted(() => {
|
|
fetchCleanedFile('pengabdian', 'pengabdian_cleaned.xlsx', pengabdian)
|
|
fetchCleanedFile('penelitian', 'penelitian_cleaned.xlsx', penelitian)
|
|
fetchCleanedFile('hki', 'hki_cleaned.xlsx', hki)
|
|
fetchCleanedFile('scholar', 'scholar_cleaned.xlsx', scholar)
|
|
fetchCleanedFile('scopus', 'scopus_cleaned.xlsx', scopus)
|
|
getYearsFromData();
|
|
renderPieChart();
|
|
})
|
|
|
|
function refreshData() {
|
|
fetchCleanedFile('pengabdian', 'pengabdian_cleaned.xlsx', pengabdian)
|
|
fetchCleanedFile('penelitian', 'penelitian_cleaned.xlsx', penelitian)
|
|
fetchCleanedFile('hki', 'hki_cleaned.xlsx', hki)
|
|
fetchCleanedFile('scholar', 'scholar_cleaned.xlsx', scholar)
|
|
fetchCleanedFile('scopus', 'scopus_cleaned.xlsx', scopus)
|
|
}
|
|
|
|
|
|
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)
|
|
|
|
const pengabdian = ref([])
|
|
const hki = ref([])
|
|
const penelitian = ref([])
|
|
const scopus = ref([])
|
|
const scholar = ref([])
|
|
const isModalOpen = ref(false)
|
|
const modalTitle = ref('')
|
|
const editableData = ref([])
|
|
const tableHeaders = ref([])
|
|
const currentTargetArray = ref(null)
|
|
|
|
const selectedYear = ref(new Date().getFullYear());
|
|
const availableYears = ref([]);
|
|
let pieChartInstance = null;
|
|
|
|
function clearCache() {
|
|
pengabdian.value = []
|
|
hki.value = []
|
|
penelitian.value = []
|
|
scopus.value = []
|
|
scholar.value = []
|
|
|
|
editableData.value = []
|
|
tableHeaders.value = []
|
|
currentTargetArray.value = null
|
|
|
|
selectedYear.value = null
|
|
|
|
// Clear grafik, misal render ulang grafik kosong
|
|
renderPieChart()
|
|
|
|
// Jika kamu punya fungsi render grafik bar, panggil juga refresh grafik kosongnya
|
|
// Misal:
|
|
// pengabdianChartData.value = {}
|
|
// dan update chart (tergantung cara implementasi chartmu)
|
|
console.log("Cache data cleared")
|
|
}
|
|
|
|
function refreshSingleData(type) {
|
|
switch(type) {
|
|
case 'pengabdian':
|
|
fetchCleanedFile('pengabdian', 'pengabdian_cleaned.xlsx', pengabdian);
|
|
break;
|
|
case 'hki':
|
|
fetchCleanedFile('hki', 'hki_cleaned.xlsx', hki);
|
|
break;
|
|
case 'penelitian':
|
|
fetchCleanedFile('penelitian', 'penelitian_cleaned.xlsx', penelitian);
|
|
break;
|
|
case 'scopus':
|
|
fetchCleanedFile('scopus', 'scopus_cleaned.xlsx', scopus);
|
|
break;
|
|
case 'scholar':
|
|
fetchCleanedFile('scholar', 'scholar_cleaned.xlsx', scholar);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
function getYearsFromData() {
|
|
const allYears = new Set();
|
|
[...hki.value, ...pengabdian.value, ...penelitian.value].forEach(item => {
|
|
const year = item?.Tahun || item?.tahun;
|
|
if (year) allYears.add(Number(year));
|
|
});
|
|
availableYears.value = Array.from(allYears).sort((a, b) => b - a);
|
|
}
|
|
|
|
function countDataByYear(dataset, year) {
|
|
return dataset.filter(item => {
|
|
const y = item?.Tahun || item?.tahun;
|
|
return String(y) === String(year);
|
|
}).length;
|
|
}
|
|
|
|
function renderPieChart() {
|
|
const year = selectedYear.value;
|
|
|
|
const hkiCount = countDataByYear(hki.value, year);
|
|
const pengabdianCount = countDataByYear(pengabdian.value, year);
|
|
const penelitianCount = countDataByYear(penelitian.value, year);
|
|
|
|
const ctx = document.getElementById('luaranPieChart').getContext('2d');
|
|
if (pieChartInstance) {
|
|
pieChartInstance.destroy();
|
|
}
|
|
|
|
pieChartInstance = new Chart(ctx, {
|
|
type: 'pie',
|
|
data: {
|
|
labels: ['HKI', 'Pengabdian', 'Penelitian'],
|
|
datasets: [{
|
|
label: 'Jumlah Luaran',
|
|
data: [hkiCount, pengabdianCount, penelitianCount],
|
|
backgroundColor: ['#38bdf8', '#facc15', '#34d399'],
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'bottom',
|
|
labels: {
|
|
color: 'white'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
watch([hki, pengabdian, penelitian], () => {
|
|
getYearsFromData();
|
|
renderPieChart();
|
|
}, { deep: true });
|
|
|
|
watch(selectedYear, () => {
|
|
renderPieChart();
|
|
});
|
|
|
|
function openModal(type) {
|
|
modalTitle.value = type.charAt(0).toUpperCase() + type.slice(1)
|
|
isModalOpen.value = true
|
|
|
|
let sourceData
|
|
if (type === 'pengabdian') {
|
|
sourceData = pengabdian.value
|
|
currentTargetArray.value = pengabdian
|
|
} else if (type === 'hki') {
|
|
sourceData = hki.value
|
|
currentTargetArray.value = hki
|
|
} else if (type === 'penelitian') {
|
|
sourceData = penelitian.value
|
|
currentTargetArray.value = penelitian
|
|
} else if (type === 'scopus') {
|
|
sourceData = scopus.value
|
|
currentTargetArray.value = scopus
|
|
} else if (type === 'scholar') {
|
|
sourceData = scholar.value
|
|
currentTargetArray.value = scholar
|
|
}
|
|
|
|
editableData.value = JSON.parse(JSON.stringify(sourceData))
|
|
tableHeaders.value = Object.keys(sourceData[0] || {})
|
|
}
|
|
|
|
function addRow() {
|
|
const newRow = {}
|
|
tableHeaders.value.forEach(h => newRow[h] = '')
|
|
editableData.value.push(newRow)
|
|
}
|
|
|
|
function removeRow(index) {
|
|
editableData.value.splice(index, 1)
|
|
}
|
|
|
|
async function saveChanges() {
|
|
currentTargetArray.value.value = JSON.parse(JSON.stringify(editableData.value));
|
|
isModalOpen.value = false;
|
|
|
|
const folder = modalTitle.value.toLowerCase();
|
|
const filename = modalTitle.value + '_cleaned.xlsx';
|
|
|
|
try {
|
|
await axios.post('http://localhost:8000/save-cleaned-file', {
|
|
folder,
|
|
filename,
|
|
data: editableData.value
|
|
});
|
|
alert('File berhasil disimpan dan di-overwrite di server!');
|
|
// Reload data dari server supaya update di frontend
|
|
refreshData();
|
|
} catch (error) {
|
|
console.error('Gagal menyimpan file:', error);
|
|
alert('Gagal menyimpan file. Cek console untuk detail.');
|
|
}
|
|
}
|
|
|
|
|
|
async function fetchCleanedFile(folder, filename, targetArray) {
|
|
try {
|
|
const response = await axios.get(`http://localhost:8000/cleaned-files/${folder}/${filename}`, {
|
|
responseType: 'arraybuffer'
|
|
})
|
|
|
|
const data = new Uint8Array(response.data)
|
|
const workbook = XLSX.read(data, { type: 'array' })
|
|
const sheet = workbook.Sheets[workbook.SheetNames[0]]
|
|
const json = XLSX.utils.sheet_to_json(sheet)
|
|
|
|
targetArray.value = json
|
|
} catch (error) {
|
|
console.error(`Gagal mengambil file cleaned (${folder}/${filename}):`, error)
|
|
}
|
|
}
|
|
|
|
|
|
function handleDrop(e) {
|
|
const file = e.dataTransfer.files[0]
|
|
if (file && file.type === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
|
|
const reader = new FileReader()
|
|
reader.onload = (evt) => {
|
|
const data = new Uint8Array(evt.target.result)
|
|
const workbook = XLSX.read(data, { type: 'array' })
|
|
const sheet = workbook.Sheets[workbook.SheetNames[0]]
|
|
const json = XLSX.utils.sheet_to_json(sheet)
|
|
pengabdian.value = json
|
|
}
|
|
reader.readAsArrayBuffer(file)
|
|
}
|
|
}
|
|
|
|
function handleDropScholar(e) {
|
|
const file = e.dataTransfer.files[0]
|
|
if (file && file.type === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
|
|
const reader = new FileReader()
|
|
reader.onload = (evt) => {
|
|
const data = new Uint8Array(evt.target.result)
|
|
const workbook = XLSX.read(data, { type: 'array' })
|
|
const sheet = workbook.Sheets[workbook.SheetNames[0]]
|
|
const json = XLSX.utils.sheet_to_json(sheet)
|
|
scholar.value = json
|
|
}
|
|
reader.readAsArrayBuffer(file)
|
|
}
|
|
}
|
|
|
|
const scholarChartData = computed(() => {
|
|
const citationsByYear = {}
|
|
for (const item of scholar.value) {
|
|
const year = item.Tahun || 'Unknown'
|
|
const citation = parseInt(item['Total Kutipan']) || 0
|
|
citationsByYear[year] = (citationsByYear[year] || 0) + citation
|
|
}
|
|
|
|
return {
|
|
labels: Object.keys(citationsByYear),
|
|
datasets: [
|
|
{
|
|
label: 'Total Kutipan Scholar per Tahun',
|
|
backgroundColor: '#f472b6', // pink
|
|
data: Object.values(citationsByYear),
|
|
}
|
|
]
|
|
}
|
|
})
|
|
|
|
|
|
|
|
const pengabdianChartData = computed(() => {
|
|
const countByYear = {}
|
|
for (const item of pengabdian.value) {
|
|
const year = item.Tahun || 'Unknown'
|
|
countByYear[year] = (countByYear[year] || 0) + 1
|
|
}
|
|
|
|
return {
|
|
labels: Object.keys(countByYear),
|
|
datasets: [
|
|
{
|
|
label: 'Jumlah Pengabdian per Tahun',
|
|
backgroundColor: '#facc15',
|
|
data: Object.values(countByYear),
|
|
}
|
|
]
|
|
}
|
|
})
|
|
|
|
function handleDropHKI(e) {
|
|
const file = e.dataTransfer.files[0]
|
|
if (file && file.type === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
|
|
const reader = new FileReader()
|
|
reader.onload = (evt) => {
|
|
const data = new Uint8Array(evt.target.result)
|
|
const workbook = XLSX.read(data, { type: 'array' })
|
|
const sheet = workbook.Sheets[workbook.SheetNames[0]]
|
|
const json = XLSX.utils.sheet_to_json(sheet)
|
|
hki.value = json
|
|
}
|
|
reader.readAsArrayBuffer(file)
|
|
}
|
|
}
|
|
|
|
const hkiChartData = computed(() => {
|
|
const countByYear = {}
|
|
for (const item of hki.value) {
|
|
const year = item.Tahun || 'Unknown'
|
|
countByYear[year] = (countByYear[year] || 0) + 1
|
|
}
|
|
|
|
return {
|
|
labels: Object.keys(countByYear),
|
|
datasets: [
|
|
{
|
|
label: 'Jumlah HKI per Tahun',
|
|
backgroundColor: '#38bdf8',
|
|
data: Object.values(countByYear),
|
|
}
|
|
]
|
|
}
|
|
})
|
|
function handleDropPenelitian(e) {
|
|
const file = e.dataTransfer.files[0]
|
|
if (file && file.type === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
|
|
const reader = new FileReader()
|
|
reader.onload = (evt) => {
|
|
const data = new Uint8Array(evt.target.result)
|
|
const workbook = XLSX.read(data, { type: 'array' })
|
|
const sheet = workbook.Sheets[workbook.SheetNames[0]]
|
|
const json = XLSX.utils.sheet_to_json(sheet)
|
|
penelitian.value = json
|
|
}
|
|
reader.readAsArrayBuffer(file)
|
|
}
|
|
}
|
|
const penelitianChartData = computed(() => {
|
|
const countByYear = {}
|
|
for (const item of penelitian.value) {
|
|
const year = item.Tahun || 'Unknown'
|
|
countByYear[year] = (countByYear[year] || 0) + 1
|
|
}
|
|
|
|
return {
|
|
labels: Object.keys(countByYear),
|
|
datasets: [
|
|
{
|
|
label: 'Jumlah Penelitian per Tahun',
|
|
backgroundColor: '#34d399', // hijau
|
|
data: Object.values(countByYear),
|
|
}
|
|
]
|
|
}
|
|
})
|
|
function handleDropScopus(e) {
|
|
const file = e.dataTransfer.files[0]
|
|
if (file && file.type === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
|
|
const reader = new FileReader()
|
|
reader.onload = (evt) => {
|
|
const data = new Uint8Array(evt.target.result)
|
|
const workbook = XLSX.read(data, { type: 'array' })
|
|
const sheet = workbook.Sheets[workbook.SheetNames[0]]
|
|
const json = XLSX.utils.sheet_to_json(sheet)
|
|
scopus.value = json
|
|
}
|
|
reader.readAsArrayBuffer(file)
|
|
}
|
|
}
|
|
|
|
const scopusChartData = computed(() => {
|
|
const countByQuartile = {}
|
|
for (const item of scopus.value) {
|
|
const quartile = item.Quartile || 'Unknown'
|
|
countByQuartile[quartile] = (countByQuartile[quartile] || 0) + 1
|
|
}
|
|
|
|
return {
|
|
labels: Object.keys(countByQuartile),
|
|
datasets: [
|
|
{
|
|
label: 'Jumlah Publikasi Scopus per Quartile',
|
|
backgroundColor: '#a78bfa', // ungu
|
|
data: Object.values(countByQuartile),
|
|
}
|
|
]
|
|
}
|
|
})
|
|
|
|
const BarChart = defineComponent({
|
|
name: 'BarChart',
|
|
props: ['data'],
|
|
setup(props) {
|
|
return () => h(Bar, { data: props.data, options: { responsive: true, plugins: { legend: { display: true }}} })
|
|
}
|
|
})
|
|
|
|
</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>
|