438 lines
20 KiB
PHP
438 lines
20 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
|
|
|
<title>J-Classify Tracer Study MIF</title>
|
|
<meta content="" name="description">
|
|
<meta content="" name="keywords">
|
|
|
|
<!-- Favicons -->
|
|
<link href="{{ asset('assets/img/favicon.png') }}" rel="icon">
|
|
<link href="{{ asset('assets/img/apple-touch-icon.png') }}" rel="apple-touch-icon">
|
|
|
|
<!-- Google Fonts -->
|
|
<link href="https://fonts.gstatic.com" rel="preconnect">
|
|
<link
|
|
href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i|Nunito:300,300i,400,400i,600,600i,700,700i|Poppins:300,300i,400,400i,500,500i,600,600i,700,700i"
|
|
rel="stylesheet">
|
|
|
|
<!-- Tambahkan ini di bagian <head> atau sebelum penutup </body> -->
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
|
|
|
|
|
<!-- Vendor CSS Files -->
|
|
<link href="{{ asset('assets/vendor/bootstrap/css/bootstrap.min.css') }}" rel="stylesheet">
|
|
<link href="{{ asset('assets/vendor/bootstrap-icons/bootstrap-icons.css') }}" rel="stylesheet">
|
|
<link href="{{ asset('assets/vendor/boxicons/css/boxicons.min.css') }}" rel="stylesheet">
|
|
<link href="{{ asset('assets/vendor/quill/quill.snow.css') }}" rel="stylesheet">
|
|
<link href="{{ asset('assets/vendor/quill/quill.bubble.css') }}" rel="stylesheet">
|
|
<link href="{{ asset('assets/vendor/remixicon/remixicon.css') }}" rel="stylesheet">
|
|
<link href="{{ asset('assets/vendor/simple-datatables/style.css') }}" rel="stylesheet">
|
|
|
|
<!-- Template Main CSS File -->
|
|
<link href="{{ asset('assets/css/style.css') }}" rel="stylesheet">
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<!-- ======= Header ======= -->
|
|
@include('header')
|
|
<!-- ======= Sidebar ======= -->
|
|
@include('sidebar')
|
|
|
|
<main id="main" class="main">
|
|
<div class="pagetitle">
|
|
<h1>Proses Klasifikasi</h1>
|
|
<nav>
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="index.html">Home</a></li>
|
|
<li class="breadcrumb-item">Proses</li>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
<section class="section">
|
|
<div class="row">
|
|
{{-- Notifikasi untuk memilih tahun angkatan --}}
|
|
@if(session('error'))
|
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
|
{{ session('error') }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Jangan tampilkan notifikasi info jika sudah ada hasil atau error --}}
|
|
@if(!isset($hasil_iterasi) && !session('error') && !request()->isMethod('post'))
|
|
<div class="alert alert-info alert-dismissible fade show" role="alert">
|
|
Silahkan pilih tahun angkatan terlebih dahulu untuk memulai proses klasifikasi
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Form pilih angkatan --}}
|
|
@if(isset($angkatan) && count($angkatan) > 0)
|
|
<form method="POST" action="{{ route('proses_klasifikasi_proses') }}">
|
|
@csrf
|
|
<div class="form-group">
|
|
<label for="id_angkatan">Pilih Tahun Angkatan:</label>
|
|
<select name="id_angkatan" id="id_angkatan" class="form-control">
|
|
<option value="">-- Pilih Tahun Angkatan --</option>
|
|
@foreach($angkatan as $angkatanItem)
|
|
<option value="{{ $angkatanItem->id_angkatan }}"
|
|
{{ isset($id_angkatan) && $id_angkatan == $angkatanItem->id_angkatan ? 'selected' : '' }}>
|
|
{{ $angkatanItem->tahun }}
|
|
</option>
|
|
@endforeach
|
|
</select>
|
|
<div id="angkatan-error-feedback" class="invalid-feedback">
|
|
Silahkan pilih tahun angkatan terlebih dahulu!
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary mt-2">Proses</button>
|
|
</form>
|
|
@else
|
|
<div class="alert alert-warning">
|
|
Data angkatan tidak tersedia. Silahkan tambahkan data angkatan terlebih dahulu.
|
|
</div>
|
|
@endif
|
|
|
|
@if(isset($hasil_iterasi) && !empty($hasil_iterasi))
|
|
<div class="card mb-4 mt-4">
|
|
<div class="card-header">Keterangan Klasifikasi</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-bordered">
|
|
<thead>
|
|
<tr>
|
|
<th>Cluster</th>
|
|
<th>Keterangan</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach($keterangan_klasifikasi as $key => $keterangan)
|
|
<tr>
|
|
<td>{{ $key }}</td>
|
|
<td>{{ $keterangan }}</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Tampilkan hasil iterasi jika ada --}}
|
|
@if(isset($hasil_iterasi))
|
|
<h4 class="mt-5">Hasil Iterasi</h4>
|
|
|
|
@foreach($hasil_iterasi as $iterasi => $hasil)
|
|
<div class="card mt-3">
|
|
<div class="card-header">Iterasi ke-{{ $iterasi + 1 }}</div>
|
|
<div class="card-body">
|
|
<h5>Proses K-Means - Iterasi: {{ $iterasi + 1 }}</h5>
|
|
<table class="table table-striped table-bordered">
|
|
<thead>
|
|
<tr>
|
|
<th>No</th>
|
|
<th>Nama</th>
|
|
<th>NIM</th>
|
|
<th>Tahun Angkatan</th>
|
|
<th>Pekerjaan</th>
|
|
<th>Kelompok Jabatan</th>
|
|
<th>Kualifikasi Bidang</th>
|
|
<th>Cluster</th>
|
|
<th>Klasifikasi</th>
|
|
<th>Waktu Proses</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach($hasil['data'] as $item)
|
|
<tr>
|
|
<td>{{ $loop->iteration }}</td>
|
|
<td>{{ $item['nama'] }}</td>
|
|
<td>{{ $item['nim'] }}</td>
|
|
<td>{{ $item['angkatan'] }}</td>
|
|
<td>{{ $item['pekerjaan'] }}</td>
|
|
{{-- <td>{{ $item['nama_jabatan'] ?? '-' }} ({{ $item['jabatan'] ?? '0' }})</td>
|
|
<td>{{ $item['nama_kualifikasi'] ?? '-' }} ({{ $item['kualifikasi'] ?? '0' }})</td> --}}
|
|
<td>{{ $item['jabatan'] ?? 'Tidak Ada' }}</td>
|
|
<td>{{ $item['kualifikasi'] ?? 'Tidak Ada' }}</td>
|
|
<td>{{ $item['cluster'] }}</td>
|
|
<td>{{ $item['klasifikasi'] }}</td>
|
|
<td>{{ now()->format('d-m-Y H:i') }}</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
|
|
{{-- Centroid Awal dan Akhir --}}
|
|
<h4 class="mt-4">Centroid Awal dan Akhir</h4>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h5>Centroid Awal</h5>
|
|
<table class="table table-bordered">
|
|
<thead>
|
|
<tr>
|
|
<th>Cluster</th>
|
|
<th>Jabatan</th>
|
|
<th>Kualifikasi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach($hasil_iterasi[0]['centroids'] as $key => $centroid)
|
|
<tr>
|
|
<td>{{ $key + 1 }}</td>
|
|
<td>{{ number_format($centroid['jabatan'], 2) }}</td>
|
|
<td>{{ number_format($centroid['kualifikasi'], 2) }}</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h5>Centroid Akhir</h5>
|
|
<table class="table table-bordered">
|
|
<thead>
|
|
<tr>
|
|
<th>Cluster</th>
|
|
<th>Jabatan</th>
|
|
<th>Kualifikasi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach($centroid_akhir as $key => $centroid)
|
|
<tr>
|
|
<td>{{ $key + 1 }}</td>
|
|
<td>{{ number_format($centroid['jabatan'], 2) }}</td>
|
|
<td>{{ number_format($centroid['kualifikasi'], 2) }}</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Tombol Simpan --}}
|
|
@if(isset($finalData) && count($finalData) > 0)
|
|
<div class="mt-4">
|
|
<form action="{{ route('simpan_hasil_klasifikasi') }}" method="POST">
|
|
@csrf
|
|
{{-- Debugging data yang akan dikirim --}}
|
|
@foreach ($finalData as $item)
|
|
<input type="hidden" name="data[{{ $loop->index }}][id]" value="{{ $item['id'] }}">
|
|
<input type="hidden" name="data[{{ $loop->index }}][cluster]" value="{{ $item['cluster'] }}">
|
|
{{-- Menampilkan debug data --}}
|
|
{{-- {{ dd($item) }} --}}
|
|
@endforeach
|
|
|
|
|
|
|
|
<input type="hidden" name="centroid_awal" value="{{ $centroid_awal }}">
|
|
<input type="hidden" name="centroid_akhir" value="{{ $centroid_akhir_json }}">
|
|
<input type="hidden" name="iterasi" value="{{ $jumlah_iterasi }}">
|
|
<input type="hidden" name="id_angkatan" value="{{ $id_angkatan }}">
|
|
|
|
<button type="submit" class="btn btn-success">Simpan Hasil Klasifikasi</button>
|
|
</form>
|
|
</div>
|
|
@endif
|
|
@endif
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ======= Footer ======= -->
|
|
@include('footer')
|
|
|
|
<a href="#" class="back-to-top d-flex align-items-center justify-content-center"><i
|
|
class="bi bi-arrow-up-short"></i></a>
|
|
|
|
<!-- Vendor JS Files -->
|
|
<script src="{{ asset('assets/vendor/apexcharts/apexcharts.min.js') }}"></script>
|
|
<script src="{{ asset('assets/vendor/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
|
|
<script src="{{ asset('assets/vendor/chart.js/chart.umd.js') }}"></script>
|
|
<script src="{{ asset('assets/vendor/echarts/echarts.min.js') }}"></script>
|
|
<script src="{{ asset('assets/vendor/quill/quill.js') }}"></script>
|
|
<script src="{{ asset('assets/vendor/simple-datatables/simple-datatables.js') }}"></script>
|
|
<script src="{{ asset('assets/vendor/tinymce/tinymce.min.js') }}"></script>
|
|
<script src="{{ asset('assets/vendor/php-email-form/validate.js') }}"></script>
|
|
|
|
<!-- Template Main JS File -->
|
|
<script src="{{ asset('assets/js/main.js') }}"></script>
|
|
|
|
<script>
|
|
document.getElementById('searchInput').addEventListener('keyup', function() {
|
|
let filter = this.value.toLowerCase();
|
|
let rows = document.querySelectorAll('tbody tr');
|
|
|
|
rows.forEach(row => {
|
|
let text = row.innerText.toLowerCase();
|
|
row.style.display = text.includes(filter) ? '' : 'none';
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<!-- Ini buat datatables Custom Rownya dengan Sorting --> <!--Pencarian-->
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function() {
|
|
const table = document.getElementById("userTable");
|
|
const tbody = table.querySelector("tbody");
|
|
const rows = Array.from(tbody.querySelectorAll("tr"));
|
|
const headers = table.querySelectorAll("thead th");
|
|
const paginationContainer = document.createElement("div");
|
|
paginationContainer.className = "pagination-container mt-3 d-flex justify-content-end";
|
|
|
|
const searchInput = document.getElementById("searchInput");
|
|
const entriesPerPageSelect = document.getElementById("entriesPerPage");
|
|
|
|
let currentPage = 1;
|
|
let rowsPerPage = parseInt(entriesPerPageSelect.value) || 5;
|
|
let currentSortColumn = null;
|
|
let currentSortDirection = "asc";
|
|
|
|
function filterRows() {
|
|
const filter = searchInput.value.toLowerCase();
|
|
return rows.filter(row => row.innerText.toLowerCase().includes(filter));
|
|
}
|
|
|
|
function sortRows(columnIndex, direction) {
|
|
rows.sort((a, b) => {
|
|
const aText = a.cells[columnIndex].innerText.trim().toLowerCase();
|
|
const bText = b.cells[columnIndex].innerText.trim().toLowerCase();
|
|
|
|
if (!isNaN(aText) && !isNaN(bText)) {
|
|
return direction === "asc" ? aText - bText : bText - aText;
|
|
}
|
|
|
|
return direction === "asc" ? aText.localeCompare(bText) : bText.localeCompare(aText);
|
|
});
|
|
}
|
|
|
|
function displayTablePage(page) {
|
|
const filteredRows = filterRows();
|
|
const totalPages = Math.ceil(filteredRows.length / rowsPerPage);
|
|
|
|
if (page > totalPages) page = totalPages;
|
|
if (page < 1) page = 1;
|
|
|
|
tbody.innerHTML = "";
|
|
|
|
if (entriesPerPageSelect.value === "all") {
|
|
filteredRows.forEach(row => tbody.appendChild(row));
|
|
} else {
|
|
const startIndex = (page - 1) * rowsPerPage;
|
|
const endIndex = startIndex + rowsPerPage;
|
|
|
|
const rowsToDisplay = filteredRows.slice(startIndex, endIndex);
|
|
rowsToDisplay.forEach(row => tbody.appendChild(row));
|
|
|
|
updatePagination(page, totalPages);
|
|
}
|
|
}
|
|
|
|
function updatePagination(currentPage, totalPages) {
|
|
paginationContainer.innerHTML = "";
|
|
|
|
if (totalPages <= 1) return;
|
|
|
|
for (let i = 1; i <= totalPages; i++) {
|
|
const button = document.createElement("button");
|
|
button.textContent = i;
|
|
button.className = "btn btn-sm btn-outline-primary mx-1";
|
|
if (i === currentPage) button.classList.add("active");
|
|
|
|
button.addEventListener("click", () => {
|
|
displayTablePage(i);
|
|
});
|
|
|
|
paginationContainer.appendChild(button);
|
|
}
|
|
}
|
|
|
|
headers.forEach((header, index) => {
|
|
header.addEventListener("click", () => {
|
|
if (currentSortColumn === index) {
|
|
currentSortDirection = currentSortDirection === "asc" ? "desc" : "asc";
|
|
} else {
|
|
currentSortColumn = index;
|
|
currentSortDirection = "asc";
|
|
}
|
|
|
|
sortRows(index, currentSortDirection);
|
|
displayTablePage(1);
|
|
});
|
|
});
|
|
|
|
searchInput.addEventListener("keyup", () => displayTablePage(1));
|
|
entriesPerPageSelect.addEventListener("change", function() {
|
|
rowsPerPage = entriesPerPageSelect.value === "all" ? rows.length : parseInt(
|
|
entriesPerPageSelect.value);
|
|
displayTablePage(1);
|
|
});
|
|
|
|
displayTablePage(1);
|
|
table.parentNode.appendChild(paginationContainer);
|
|
});
|
|
</script>
|
|
|
|
<!-- Script untuk validasi form -->
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const form = document.querySelector('form[action="{{ route("proses_klasifikasi_proses") }}"]');
|
|
if (form) {
|
|
// Hapus atribut required untuk mencegah validasi HTML5 default
|
|
const angkatanSelect = document.getElementById('id_angkatan');
|
|
if (angkatanSelect) {
|
|
angkatanSelect.removeAttribute('required');
|
|
}
|
|
|
|
form.addEventListener('submit', function(e) {
|
|
e.preventDefault(); // Selalu mencegah submit default
|
|
|
|
if (!angkatanSelect.value) {
|
|
// Tampilkan alert kustom
|
|
alert('Silahkan pilih tahun angkatan terlebih dahulu!');
|
|
|
|
// Tambahkan class untuk gaya visual error
|
|
angkatanSelect.classList.add('is-invalid');
|
|
} else {
|
|
// Jika valid, hapus class error
|
|
angkatanSelect.classList.remove('is-invalid');
|
|
|
|
// Submit form secara manual
|
|
form.removeEventListener('submit', arguments.callee);
|
|
form.submit();
|
|
}
|
|
});
|
|
|
|
// Hapus pesan error saat user mengubah pilihan
|
|
angkatanSelect.addEventListener('change', function() {
|
|
if (this.value !== '') {
|
|
this.classList.remove('is-invalid');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
</body>
|
|
|
|
</html>
|