cite_clasify/resources/views/dashboard/entry/index.blade.php

479 lines
22 KiB
PHP

@extends('layouts.dashboard')
@section('content')
<div class="toolbar py-5 py-lg-15" id="kt_toolbar">
<div id="kt_toolbar_container" class="container-xxl d-flex flex-stack flex-wrap">
<div class="page-title d-flex flex-column me-3">
<h1 class="d-flex text-white fw-bolder my-1 fs-3">Dashboard</h1>
<ul class="breadcrumb breadcrumb-separatorless fw-bold fs-7 my-1">
<li class="breadcrumb-item text-white opacity-75">
<a href="{{ route('dashboard.index', ['id' => 1]) }}" class="text-white text-hover-primary">Home</a>
</li>
<li class="breadcrumb-item">
<span class="bullet bg-white opacity-75 w-5px h-2px"></span>
</li>
<li class="breadcrumb-item text-white opacity-75">Dashboard</li>
</ul>
</div>
</div>
</div>
<div id="kt_content_container" class="d-flex flex-column-fluid align-items-start container-xxl">
<div class="content flex-row-fluid" id="kt_content">
<div class="row g-5 g-xxl-8">
<div class="card">
<div class="card-body">
<form action="{{ route('dashboard.entry.upload') }}" class="dropzone" id="journal-dropzone"
enctype="multipart/form-data">
@csrf
<div class="dz-message" data-dz-message><span>Drop journal PDF here or click to upload.</span>
</div>
</form>
</div>
</div>
<div class="card mt-5">
<div class="card-body">
<!-- Keterangan warna node -->
<div class="mb-3">
<strong>Keterangan Warna Node:</strong>
<span
style="background:#1E90FF; color:#fff; padding:2px 8px; border-radius:4px; margin-right:8px;">Keyword
</span>
<span
style="background:#FF6347; color:#fff; padding:2px 8px; border-radius:4px; margin-right:8px;">Article
</span>
<span
style="background:#32CD32; color:#fff; padding:2px 8px; border-radius:4px; margin-right:8px;">Author
</span>
<span
style="background:#9370DB; color:#fff; padding:2px 8px; border-radius:4px; margin-right:8px;">Affiliation
</span>
<span
style="background:#FFA500; color:#fff; padding:2px 8px; border-radius:4px; margin-right:8px;">Journal
</span>
<span
style="background:#708090; color:#fff; padding:2px 8px; border-radius:4px; margin-right:8px;">DOI
</span>
<span
style="background:#00CED1; color:#fff; padding:2px 8px; border-radius:4px; margin-right:8px;">Open
</span>
</div>
<div id="cy" style="width: 100%; height: 600px; border: 1px solid #ccc;"></div>
<div id="article-grid" class="mt-5">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="titleModal" tabindex="-1" aria-labelledby="titleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form id="titleForm">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="titleModalLabel">Isi Judul Artikel</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="text" class="form-control" id="articleTitle" name="title"
placeholder="Masukkan judul artikel" required>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Kirim Analisis</button>
</div>
</div>
</form>
</div>
</div>
@endsection
@push('js')
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.9.3/min/dropzone.min.js"></script>
<!-- jQuery dulu -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- qTip2 CSS dan JS -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/qtip2/3.0.3/jquery.qtip.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/qtip2/3.0.3/jquery.qtip.min.js"></script>
<!-- Cytoscape -->
<script src="https://unpkg.com/cytoscape@3.24.0/dist/cytoscape.min.js"></script>
<!-- cytoscape-qtip plugin -->
<script src="https://cdn.jsdelivr.net/npm/cytoscape-qtip@2.7.0/cytoscape-qtip.min.js"></script>
<script>
let uploadedFile = null;
Dropzone.options.journalDropzone = {
paramName: 'file',
maxFilesize: 5, // MB
acceptedFiles: '.pdf',
dictDefaultMessage: 'Drop journal PDF here or click to upload',
sending: function (file, xhr, formData) {
Swal.fire({
title: 'Uploading...',
text: 'Mohon tunggu, jurnal sedang diproses.',
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
},
success: function (file, response) {
Swal.close();
if (response.keywords && response.results) {
renderGraph(response.keywords, response.results);
uploadedFile = file;
let titleModal = new bootstrap.Modal(document.getElementById('titleModal'));
titleModal.show();
} else {
alert('Response tidak mengandung data yang valid!');
}
},
error: function (file, response) {
Swal.close();
alert("Upload error: " + (response.error || response));
}
};
function normalize(text) {
return text.toLowerCase().replace(/[^a-z0-9]+/g, '');
}
function renderGraph(keywords, results) {
document.getElementById('cy').innerHTML = '';
let nodes = [];
let edges = [];
let nodeIds = new Set();
function truncateLabel(label) {
return label && label.length > 30 ? label.substr(0, 27) + '...' : label;
}
function getNodeSizeByText(text, minSize = 40, maxSize = 150, charMultiplier = 6) {
const length = text.length;
return Math.min(maxSize, Math.max(minSize, length * charMultiplier));
}
keywords.forEach((kw, i) => {
let id = 'kw' + i;
let size = getNodeSizeByText(kw);
nodes.push({
data: { id, label: truncateLabel(kw), fullLabel: kw, type: 'keyword' },
style: { width: size, height: size }
});
nodeIds.add(id);
});
results.forEach((article, i) => {
let artId = 'art' + i;
let title = article['dc:title'] || article.title || 'Judul tidak diketahui';
let artSize = getNodeSizeByText(title, 40, 100, 4);
if (!nodeIds.has(artId)) {
nodes.push({
data: { id: artId, label: truncateLabel(title), fullLabel: title, type: 'article' },
style: { width: artSize, height: artSize }
});
nodeIds.add(artId);
}
keywords.forEach((kw, j) => {
if (normalize(title).includes(normalize(kw))) {
edges.push({
data: { id: `e_kw${j}_art${i}`, source: 'kw' + j, target: artId }
});
}
});
if (article['dc:creator']) {
let authors = Array.isArray(article['dc:creator']) ? article['dc:creator'] : [article['dc:creator']];
authors.forEach((authorName, idx) => {
let authId = `auth${i}_${idx}`;
let authSize = getNodeSizeByText(authorName);
if (!nodeIds.has(authId)) {
nodes.push({
data: { id: authId, label: truncateLabel(authorName), fullLabel: authorName, type: 'author' },
style: { width: authSize, height: authSize }
});
nodeIds.add(authId);
}
edges.push({
data: { id: `e_art${i}_auth${i}_${idx}`, source: artId, target: authId, label: 'written by' }
});
if (article.affiliation && article.affiliation.length > 0) {
article.affiliation.forEach((affil, affilIdx) => {
let affilName = affil.affilname || 'Unknown Affiliation';
let affilId = `affil${i}_${affilIdx}`;
let affilSize = getNodeSizeByText(affilName);
if (!nodeIds.has(affilId)) {
nodes.push({
data: { id: affilId, label: truncateLabel(affilName), fullLabel: affilName, type: 'affiliation' },
style: { width: affilSize, height: affilSize }
});
nodeIds.add(affilId);
}
edges.push({
data: { id: `e_auth${i}_${idx}_affil${i}_${affilIdx}`, source: authId, target: affilId, label: 'affiliated with' }
});
});
}
});
}
if (article['prism:publicationName']) {
let journalName = article['prism:publicationName'];
let journalId = `journal${i}`;
let journalSize = getNodeSizeByText(journalName, 40, 100, 4);
if (!nodeIds.has(journalId)) {
nodes.push({
data: { id: journalId, label: truncateLabel(journalName), fullLabel: journalName, type: 'journal' },
style: { width: journalSize, height: journalSize }
});
nodeIds.add(journalId);
}
edges.push({
data: { id: `e_art${i}_journal${i}`, source: artId, target: journalId, label: 'published in' }
});
}
if (article['prism:doi']) {
let doi = article['prism:doi'];
let doiId = `doi${i}`;
let doiSize = getNodeSizeByText(doi, 40, 80, 5);
if (!nodeIds.has(doiId)) {
nodes.push({
data: { id: doiId, label: truncateLabel(doi), fullLabel: doi, type: 'doi' },
style: { width: doiSize, height: 30 }
});
nodeIds.add(doiId);
}
edges.push({
data: { id: `e_art${i}_doi${i}`, source: artId, target: doiId, label: 'has DOI' }
});
}
if (article.openaccessFlag) {
let oaId = `oa${i}`;
if (!nodeIds.has(oaId)) {
nodes.push({
data: { id: oaId, label: 'Open Access', fullLabel: 'Open Access', type: 'openaccess' },
style: { width: 100, height: 30 }
});
nodeIds.add(oaId);
}
edges.push({
data: { id: `e_art${i}_oa${i}`, source: artId, target: oaId, label: 'open access' }
});
}
});
let cy = cytoscape({
container: document.getElementById('cy'),
elements: { nodes, edges },
style: [
{
selector: 'node',
style: {
label: 'data(label)',
color: '#fff',
'text-valign': 'center',
'text-halign': 'center',
'font-size': '10px',
'text-wrap': 'wrap',
'text-max-width': '100px'
}
},
{ selector: 'node[type="keyword"]', style: { 'background-color': '#1E90FF', 'shape': 'roundrectangle', 'border-color': '#0a53b6', 'border-width': 2 } },
{ selector: 'node[type="article"]', style: { 'background-color': '#FF6347', 'shape': 'ellipse' } },
{ selector: 'node[type="author"]', style: { 'background-color': '#32CD32', 'shape': 'ellipse' } },
{ selector: 'node[type="affiliation"]', style: { 'background-color': '#9370DB', 'shape': 'roundrectangle' } },
{ selector: 'node[type="journal"]', style: { 'background-color': '#FFA500', 'shape': 'triangle' } },
{ selector: 'node[type="doi"]', style: { 'background-color': '#708090', 'shape': 'roundrectangle' } },
{ selector: 'node[type="openaccess"]', style: { 'background-color': '#00CED1', 'shape': 'rectangle' } },
{
selector: 'edge',
style: {
'width': 1,
'line-color': '#bbb',
'target-arrow-color': '#bbb',
'target-arrow-shape': 'triangle',
'curve-style': 'bezier',
'label': 'data(label)',
'font-size': '7px',
'text-rotation': 'autorotate',
'color': '#555'
}
}
],
layout: {
name: 'cose',
idealEdgeLength: 100,
nodeOverlap: 20,
refresh: 20,
fit: true,
padding: 30,
randomize: true,
componentSpacing: 100,
nodeRepulsion: 400000,
edgeElasticity: 100,
nestingFactor: 5,
gravity: 80,
numIter: 1000,
initialTemp: 200,
coolingFactor: 0.95,
minTemp: 1.0
}
});
cy.nodes().forEach(ele => {
if (ele.data('type') === 'doi') {
let doi = ele.data('fullLabel');
let doiUrl = doi.startsWith('10.') ? 'https://doi.org/' + doi : '#';
ele.qtip({
content: `<a href="${doiUrl}" target="_blank" style="color:#00f; text-decoration:underline;">${doi}</a>`,
position: { my: 'top center', at: 'bottom center' },
style: { classes: 'qtip-bootstrap', tip: { width: 10, height: 8 } },
show: { event: 'mouseover' },
hide: { event: 'mouseout' }
});
ele.on('tap', () => window.open(doiUrl, '_blank'));
} else {
ele.qtip({
content: ele.data('fullLabel'),
position: { my: 'top center', at: 'bottom center' },
style: { classes: 'qtip-bootstrap', tip: { width: 10, height: 8 } }
});
}
});
let gridContainer = document.getElementById('article-grid');
gridContainer.innerHTML = '';
let gridHTML = `
<h4>Hasil Pencarian Artikel</h4>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
`;
results.forEach((article, i) => {
let title = article['dc:title'] || article.title || 'Judul tidak diketahui';
let authors = Array.isArray(article['dc:creator']) ? article['dc:creator'].join(', ') : article['dc:creator'] || 'Penulis tidak diketahui';
let journal = article['prism:publicationName'] || 'Jurnal tidak diketahui';
let doi = article['prism:doi'] ? `<a href="https://doi.org/${article['prism:doi']}" target="_blank">${article['prism:doi']}</a>` : '—';
gridHTML += `
<div class="col">
<div class="card shadow-sm h-100">
<div class="card-body">
<h6 class="card-title fw-bold">${title}</h6>
<p class="mb-1"><strong>Author:</strong> ${authors}</p>
<p class="mb-1"><strong>Journal:</strong> ${journal}</p>
<p class="mb-0"><strong>DOI:</strong> ${doi}</p>
</div>
</div>
</div>
`;
});
gridHTML += `</div>`;
gridContainer.innerHTML = gridHTML;
document.getElementById('titleForm').addEventListener('submit', function (e) {
e.preventDefault();
const titleInput = document.getElementById('articleTitle').value.trim();
if (!titleInput) {
alert('Judul artikel wajib diisi!');
return;
}
if (!uploadedFile) {
alert('File tidak ditemukan, silakan upload ulang.');
return;
}
// Buat FormData baru untuk POST
let formData = new FormData();
formData.append('file', uploadedFile);
formData.append('title', titleInput);
// Tampilkan Swal loading sebelum kirim
Swal.fire({
title: 'Mengirim data...',
text: 'Mohon tunggu, sedang memproses analisis jurnal.',
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
fetch('{{ route("dashboard.journal.store") }}', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
},
body: formData
})
.then(res => {
if (!res.ok) throw new Error('Network response was not ok');
return res.json();
})
.then(data => {
Swal.close();
let modalEl = document.getElementById('titleModal');
let modal = bootstrap.Modal.getInstance(modalEl);
modal.hide();
Swal.fire({
title: 'Sukses!',
text: data.message || 'Analisis jurnal berhasil dikirim.',
icon: 'success',
showCancelButton: true,
confirmButtonText: 'Lihat Hasil',
cancelButtonText: 'Tutup'
}).then((result) => {
if (result.isConfirmed) {
if (data.journal_id) {
window.open(`{{ url('dashboard/journal') }}/${data.journal_id}`, '_blank');
}
}
});
})
.catch(err => {
Swal.close();
Swal.fire('Error!', 'Terjadi kesalahan saat mengirim data.', 'error');
});
});
}
</script>
@endpush