Add simulasi-mk.html

This commit is contained in:
izu
2026-01-25 18:33:45 +00:00
parent 3b4f5f58e2
commit 55699a4245

651
simulasi-mk.html Normal file
View File

@@ -0,0 +1,651 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simulasi KRS & Student Journey</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');
body { font-family: 'Inter', sans-serif; }
.chart-container {
position: relative;
width: 100%;
max-width: 600px;
margin-left: auto;
margin-right: auto;
height: 300px;
max-height: 400px;
}
@media (min-width: 768px) { .chart-container { height: 350px; } }
.course-card { transition: all 0.2s ease; cursor: grab; user-select: none; }
.course-card:active { cursor: grabbing; }
.course-card:hover { transform: translateY(-2px); box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); }
.course-card.dragging { opacity: 0.5; border: 2px dashed #a8a29e; background-color: #f5f5f4; }
/* Drop Zone Styles */
.semester-list { min-height: 120px; transition: background-color 0.2s; }
.semester-list.drag-over { background-color: #f0fdf4; border: 2px dashed #4ade80; }
/* Warning States */
.semester-container { transition: all 0.3s; }
.semester-container.overload { border-color: #ef4444; background-color: #fef2f2; }
.semester-container.overload .header-bg { background-color: #fee2e2; border-bottom-color: #fca5a5; }
.semester-container.overload .sks-badge { background-color: #ef4444; color: white; border-color: #ef4444; }
.track-btn.active { background-color: #44403c; color: white; border-color: #44403c; }
.track-btn { background-color: #f5f5f4; color: #78716c; border: 1px solid #e7e5e4; }
.fade-in { animation: fadeIn 0.5s ease-in-out; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
/* Range Slider Styling */
input[type=range] {
-webkit-appearance: none; width: 100%; background: transparent;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none; height: 16px; width: 16px; border-radius: 50%; background: #44403c; cursor: pointer; margin-top: -6px;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%; height: 4px; cursor: pointer; background: #d6d3d1; border-radius: 2px;
}
</style>
</head>
<body class="bg-stone-50 text-stone-800">
<!-- Chosen Palette: Warm Neutrals (Stone) with Logic-based Alerts (Red/Green) -->
<!-- Application Structure Plan:
- Added "GPA Simulation" controls to each semester header.
- Semester containers now react visually to SKS Overload based on previous semester's GPA.
- Drag & Drop enables users to "Fail/Postpone" courses to future semesters to fit the SKS limit.
-->
<!-- CONFIRMATION: NO SVG graphics used. NO Mermaid JS used. -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Header -->
<header class="mb-10 text-center">
<h1 class="text-3xl md:text-4xl font-bold text-stone-900 mb-4">Simulasi Perjalanan Mahasiswa & KRS</h1>
<p class="text-lg text-stone-600 max-w-3xl mx-auto">
Atur strategi studimu. <span class="font-bold text-orange-600">Simulasikan IP rendah</span>, lihat dampak batas SKS, dan geser mata kuliah untuk menyusun ulang rencana (mengulang/menunda).
</p>
<div class="mt-6 flex flex-wrap justify-center gap-4 text-sm">
<div class="flex items-center gap-2 bg-white px-3 py-1.5 rounded border border-stone-200 shadow-sm">
<div class="w-3 h-3 rounded-full bg-green-500"></div>
<span>IP &ge; 3.00 (Max 24 SKS)</span>
</div>
<div class="flex items-center gap-2 bg-white px-3 py-1.5 rounded border border-stone-200 shadow-sm">
<div class="w-3 h-3 rounded-full bg-yellow-400"></div>
<span>2.50 &le; IP < 3.00 (Max 21 SKS)</span>
</div>
<div class="flex items-center gap-2 bg-white px-3 py-1.5 rounded border border-stone-200 shadow-sm">
<div class="w-3 h-3 rounded-full bg-orange-400"></div>
<span>2.00 &le; IP < 2.50 (Max 18 SKS)</span>
</div>
<div class="flex items-center gap-2 bg-white px-3 py-1.5 rounded border border-stone-200 shadow-sm">
<div class="w-3 h-3 rounded-full bg-red-500"></div>
<span>IP < 2.00 (Max 15 SKS)</span>
</div>
</div>
</header>
<!-- Phase 1: Generalist (Sem 1-4) -->
<section class="mb-12">
<div class="flex items-center justify-between mb-6 border-b border-stone-200 pb-2">
<h2 class="text-2xl font-bold text-stone-800">Tahun 1 & 2 (Generalist)</h2>
<div class="text-xs text-stone-500 italic">Geser slider IP untuk melihat perubahan Max SKS semester depan</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<!-- Generate Semester Containers dynamically to handle logic easier -->
<div id="container-sem-1"></div>
<div id="container-sem-2"></div>
<div id="container-sem-3"></div>
<div id="container-sem-4"></div>
</div>
</section>
<!-- Phase 2: Specialist (Sem 5-8) -->
<section class="mb-16">
<div class="flex flex-col md:flex-row items-start md:items-center justify-between mb-6">
<div class="mb-4 md:mb-0">
<h2 class="text-2xl font-bold text-stone-800">Tahun 3 & 4 (Specialist)</h2>
<p class="text-stone-500 text-sm">Pilih jalur, lalu atur beban studi jika terkena dampak IP rendah.</p>
</div>
<div class="flex flex-wrap gap-2">
<button onclick="switchTrack('ai')" id="btn-ai" class="track-btn active px-4 py-2 rounded-lg text-sm font-semibold transition-colors">Komputasi & AI</button>
<button onclick="switchTrack('rpl')" id="btn-rpl" class="track-btn px-4 py-2 rounded-lg text-sm font-semibold transition-colors">RPL</button>
<button onclick="switchTrack('net')" id="btn-net" class="track-btn px-4 py-2 rounded-lg text-sm font-semibold transition-colors">Jaringan</button>
<button onclick="switchTrack('si')" id="btn-si" class="track-btn px-4 py-2 rounded-lg text-sm font-semibold transition-colors">Sistem Informasi</button>
</div>
</div>
<div id="specialist-content" class="bg-stone-100 p-6 rounded-2xl border border-stone-200">
<!-- Injected via JS -->
</div>
</section>
<!-- Analytics -->
<section class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12">
<div class="bg-white p-6 rounded-xl border border-stone-200 shadow-sm">
<h3 class="text-lg font-bold text-stone-800 mb-2">Monitor Beban SKS</h3>
<p class="text-xs text-stone-500 mb-4">Batang merah menandakan beban melebihi jatah SKS akibat IP semester sebelumnya.</p>
<div class="chart-container">
<canvas id="sksChart"></canvas>
</div>
</div>
<div class="bg-white p-6 rounded-xl border border-stone-200 shadow-sm flex flex-col justify-center items-center text-center">
<h3 class="text-lg font-bold text-stone-800 mb-2">Total SKS Terambil</h3>
<div class="text-5xl font-bold text-stone-800 my-4" id="grand-total-sks">146</div>
<p class="text-sm text-stone-500">Target Kelulusan: 144 SKS</p>
<div id="graduation-status" class="mt-4 px-4 py-2 rounded-full text-sm font-bold bg-green-100 text-green-700">Siap Lulus</div>
</div>
</section>
<footer class="text-center text-stone-400 text-sm pt-8 border-t border-stone-200 pb-8">
<p>© 2026 Curriculum Simulator. Based on Verified Student Journey Map.</p>
</footer>
</div>
<!-- TEMPLATE FOR SEMESTER CARD -->
<template id="semester-template">
<div class="semester-container bg-white rounded-xl shadow-sm border border-stone-200 overflow-hidden flex flex-col h-full">
<div class="header-bg bg-stone-100 px-4 py-3 border-b border-stone-200">
<div class="flex justify-between items-center mb-2">
<h3 class="font-bold text-stone-800 text-lg sem-title">Semester X</h3>
<span class="sks-badge text-xs bg-white px-2 py-1 rounded border border-stone-300 font-bold">0 / 24 SKS</span>
</div>
<!-- GPA Simulator Control -->
<div class="bg-white/50 rounded p-2 border border-stone-200/50">
<div class="flex justify-between text-xs text-stone-600 mb-1">
<span>Simulasi IP: <strong class="gpa-value">3.50</strong></span>
<span class="text-[10px] text-stone-400">Efek ke Sem Selanjutnya</span>
</div>
<input type="range" min="0" max="4" step="0.01" value="3.50" class="gpa-slider w-full h-1 bg-stone-200 rounded-lg appearance-none cursor-pointer">
</div>
</div>
<div class="semester-list p-3 space-y-2 flex-grow" ondrop="drop(event)" ondragover="allowDrop(event)" ondragenter="dragEnter(event)" ondragleave="dragLeave(event)">
<!-- Course Cards go here -->
</div>
<div class="status-footer px-4 py-2 bg-stone-50 text-[10px] text-stone-400 border-t border-stone-100 italic text-right">
Max SKS Next: <span class="next-max-sks">24</span>
</div>
</div>
</template>
<script>
// --- DATA ---
const generalistSemestersBlueprint = {
1: [
{code: "MK06", name: "Dasar Pemrograman*", sks: 3},
{code: "MK07", name: "Algoritma & Logika", sks: 2},
{code: "MK04", name: "Kalkulus", sks: 3},
{code: "MK05", name: "Pengantar Informatika", sks: 2},
{code: "MK01", name: "Pendidikan Pancasila", sks: 2},
{code: "MK02", name: "Pendidikan Agama", sks: 3},
{code: "MK03", name: "Bahasa Inggris", sks: 2},
{code: "MK08", name: "Kom. Profesional", sks: 2}
],
2: [
{code: "MK15", name: "Struktur Data & Algo*", sks: 3},
{code: "MK12", name: "Matematika Diskrit", sks: 3},
{code: "MK11", name: "Sistem Digital", sks: 2},
{code: "MK13", name: "Org. & Ars. Komputer", sks: 3},
{code: "MK14", name: "Sistem Operasi*", sks: 3},
{code: "MK16", name: "Literasi Digital", sks: 2},
{code: "MK10", name: "Bahasa Indonesia", sks: 2},
{code: "MK09", name: "Kewarganegaraan", sks: 2}
],
3: [
{code: "MK20", name: "Basis Data*", sks: 3},
{code: "MK21", name: "Jaringan Komputer*", sks: 3},
{code: "MK27", name: "PBO*", sks: 3},
{code: "MK24", name: "IMK", sks: 2},
{code: "MK22", name: "Strategi Algoritma*", sks: 3},
{code: "MK17", name: "Aljabar Linier", sks: 3},
{code: "MK19", name: "Sistem Informasi", sks: 2},
{code: "MK18", name: "Etika & Keberlanjutan", sks: 2}
],
4: [
{code: "MK23", name: "RPL*", sks: 3},
{code: "MK32", name: "Probstat", sks: 3},
{code: "MK31", name: "Pemrograman Web*", sks: 3},
{code: "MK30", name: "Kecerdasan Buatan", sks: 3},
{code: "MK25", name: "APSI", sks: 3},
{code: "MK29", name: "Pemrog. Jaringan*", sks: 3},
{code: "MK26", name: "Sis. Paralel", sks: 2}
]
};
const tracksBlueprint = {
ai: {
name: "Komputasi & AI",
color: "text-blue-800",
desc: "Fokus: Data, ML, Vision.",
sem5: [
{code: "MK35", name: "Machine Learning", sks: 3},
{code: "MK28", name: "Metode Numerik*", sks: 3},
{code: "MK36", name: "Proyek PL*", sks: 3},
{code: "MK37", name: "Proposal TA", sks: 2},
{code: "MKP", name: "Data Mining", sks: 3},
{code: "MKP", name: "Big Data", sks: 3},
{code: "MK33", name: "IoT", sks: 3}
],
sem6: [
{code: "MK38", name: "Comp. Vision*", sks: 3},
{code: "MK43", name: "NLP", sks: 3},
{code: "MK44", name: "Kerja Praktik", sks: 2},
{code: "MKP", name: "Deep Learning", sks: 3},
{code: "MKP", name: "AI Ethics", sks: 2},
{code: "MKP", name: "Pilihan Lintas KK", sks: 3},
{code: "MK42", name: "Keamanan Info*", sks: 2}
],
sem7: [
{code: "MK48", name: "TA 1", sks: 2},
{code: "MK45", name: "Teknopreneur [K]", sks: 3},
{code: "MK47", name: "Uji PL", sks: 3},
{code: "MK42", name: "Keamanan Info", sks: 3},
{code: "MKP", name: "Health Info", sks: 3},
{code: "MKP", name: "Reinforcement L.", sks: 3},
{code: "MKP", name: "Quantum/Bebas", sks: 5}
]
},
rpl: {
name: "RPL",
color: "text-orange-800",
desc: "Fokus: Apps, Architecture, UX.",
sem5: [
{code: "MK36", name: "Proyek PL*", sks: 3},
{code: "MKP", name: "UX/UI Design", sks: 3},
{code: "MKP", name: "Secure SE", sks: 3},
{code: "MKP", name: "Web Lanjut", sks: 3},
{code: "MK28", name: "Metode Numerik*", sks: 3},
{code: "MK37", name: "Proposal TA", sks: 2},
{code: "MK33", name: "IoT", sks: 3}
],
sem6: [
{code: "MK39", name: "Pemrog. Mobile*", sks: 3},
{code: "MKP", name: "Microservices", sks: 3},
{code: "MKP", name: "Game Dev", sks: 3},
{code: "MK44", name: "Kerja Praktik", sks: 2},
{code: "MK41", name: "Sis. Enterprise", sks: 3},
{code: "MKP", name: "Pil. Lintas KK", sks: 4}
],
sem7: [
{code: "MK48", name: "TA 1", sks: 2},
{code: "MK45", name: "Teknopreneur [K]", sks: 3},
{code: "MK47", name: "Uji PL", sks: 3},
{code: "MK42", name: "Keamanan Info", sks: 3},
{code: "MKP", name: "DevOps", sks: 3},
{code: "MKP", name: "Soft. Arch", sks: 3},
{code: "MKP", name: "Pil. Bebas", sks: 5}
]
},
net: {
name: "Jaringan",
color: "text-emerald-800",
desc: "Fokus: Infra, Security, IoT.",
sem5: [
{code: "MK33", name: "IoT", sks: 3},
{code: "MK34", name: "Manajemen Jar.*", sks: 3},
{code: "MKP", name: "Embedded Sys", sks: 3},
{code: "MKP", name: "Ethical Hack", sks: 3},
{code: "MK36", name: "Proyek PL*", sks: 3},
{code: "MK28", name: "Metode Numerik*", sks: 3},
{code: "MK37", name: "Proposal TA", sks: 2}
],
sem6: [
{code: "MK42", name: "Keamanan Info*", sks: 3},
{code: "MKP", name: "Wireless Sensor", sks: 3},
{code: "MKP", name: "Cryptography", sks: 3},
{code: "MK44", name: "Kerja Praktik", sks: 2},
{code: "MK39", name: "Mobile (IoT)*", sks: 3},
{code: "MKP", name: "Pil. Lintas KK", sks: 3}
],
sem7: [
{code: "MK48", name: "TA 1", sks: 2},
{code: "MK45", name: "Teknopreneur [K]", sks: 3},
{code: "MK47", name: "Uji PL", sks: 3},
{code: "MKP", name: "Cloud IoT", sks: 3},
{code: "MKP", name: "Blockchain", sks: 3},
{code: "MKP", name: "Pil. Bebas 1", sks: 3},
{code: "MKP", name: "Pil. Bebas 2", sks: 6}
]
},
si: {
name: "Sistem Informasi",
color: "text-purple-800",
desc: "Fokus: Business, GIS, Governance.",
sem5: [
{code: "MKP", name: "Business Intel", sks: 3},
{code: "MKP", name: "GIS II", sks: 3},
{code: "MK36", name: "Proyek PL*", sks: 3},
{code: "MK28", name: "Metode Numerik*", sks: 3},
{code: "MK37", name: "Proposal TA", sks: 2},
{code: "MKP", name: "E-Business", sks: 3},
{code: "MK33", name: "IoT", sks: 3}
],
sem6: [
{code: "MK41", name: "Sis. Enterprise", sks: 3},
{code: "MK40", name: "SIG", sks: 3},
{code: "MKP", name: "Enterprise Arch", sks: 3},
{code: "MKP", name: "Smart City", sks: 3},
{code: "MK44", name: "Kerja Praktik", sks: 2},
{code: "MKP", name: "Pil. Lintas KK", sks: 3}
],
sem7: [
{code: "MK48", name: "TA 1", sks: 2},
{code: "MK45", name: "Teknopreneur [K]", sks: 3},
{code: "MK47", name: "Uji PL", sks: 3},
{code: "MK42", name: "Keamanan Info", sks: 3},
{code: "MKP", name: "SCM", sks: 3},
{code: "MKP", name: "IT Governance", sks: 3},
{code: "MKP", name: "Pil. Bebas", sks: 6}
]
}
};
const mbkmData = [
{code: "MK46", name: "PMKM (Wajib)", sks: 2},
{code: "MK45", name: "Teknopreneur (Konversi)", sks: 3},
{code: "MKP", name: "Pilihan Bebas (Ops)", sks: 3}
];
// --- STATE ---
let currentPlan = {};
let currentTrack = 'ai';
let semesterStats = {
1: { gpa: 3.50, maxSKSNext: 24 }, // Sem 1 Max is fixed (usually paket)
2: { gpa: 3.50, maxSKSNext: 24 },
3: { gpa: 3.50, maxSKSNext: 24 },
4: { gpa: 3.50, maxSKSNext: 24 },
5: { gpa: 3.50, maxSKSNext: 24 },
6: { gpa: 3.50, maxSKSNext: 24 },
7: { gpa: 3.50, maxSKSNext: 24 },
8: { gpa: 4.00, maxSKSNext: 24 }
};
// --- LOGIC ---
function calculateMaxSKS(gpa) {
if (gpa >= 3.00) return 24;
if (gpa >= 2.50) return 21;
if (gpa >= 2.00) return 18;
return 15;
}
function initPlan(trackKey) {
currentTrack = trackKey;
currentPlan = {};
// Deep Copy Gen
for(let i=1; i<=4; i++) currentPlan[i] = JSON.parse(JSON.stringify(generalistSemestersBlueprint[i]));
// Deep Copy Spec
const track = tracksBlueprint[trackKey];
currentPlan[5] = JSON.parse(JSON.stringify(track.sem5));
currentPlan[6] = JSON.parse(JSON.stringify(track.sem6));
currentPlan[7] = JSON.parse(JSON.stringify(track.sem7));
currentPlan[8] = [{code: "MK49", name: "TA 2 (Sidang)", sks: 4}];
}
function renderSem(semId, containerId) {
const container = document.getElementById(containerId);
if (!container) return;
// Determine Max SKS for THIS semester based on PREV semester GPA
let maxSKS = 24;
if (semId > 1) {
const prevGPA = semesterStats[semId-1].gpa;
maxSKS = calculateMaxSKS(prevGPA);
}
// Get Current SKS Load
const courses = currentPlan[semId];
const currentSKS = courses.reduce((a,b)=>a+b.sks, 0);
// Warning State
const isOverload = currentSKS > maxSKS;
// Template Cloning
const template = document.getElementById('semester-template');
const clone = template.content.cloneNode(true);
// Populate Header
const titleEl = clone.querySelector('.sem-title');
titleEl.textContent = `Semester ${semId}`;
const badgeEl = clone.querySelector('.sks-badge');
badgeEl.textContent = `${currentSKS} / ${maxSKS} SKS`;
const containerDiv = clone.querySelector('.semester-container');
if (isOverload) {
containerDiv.classList.add('overload');
badgeEl.textContent += " (OVER!)";
}
// GPA Control
const gpaValEl = clone.querySelector('.gpa-value');
const gpaSlider = clone.querySelector('.gpa-slider');
const nextMaxEl = clone.querySelector('.next-max-sks');
gpaSlider.value = semesterStats[semId].gpa;
gpaValEl.textContent = parseFloat(semesterStats[semId].gpa).toFixed(2);
// Calc next sem limit based on current slider
const nextLimit = calculateMaxSKS(semesterStats[semId].gpa);
if (semId < 8) {
nextMaxEl.textContent = nextLimit;
} else {
nextMaxEl.parentElement.style.display = 'none';
}
// Event Listener for Slider
gpaSlider.addEventListener('input', (e) => {
const val = parseFloat(e.target.value);
semesterStats[semId].gpa = val;
gpaValEl.textContent = val.toFixed(2);
// Update display of next limit immediately
if(semId < 8) nextMaxEl.textContent = calculateMaxSKS(val);
// Trigger re-render of NEXT semester (to show overload warning)
// We re-render everything to be safe and simple
// Debounce slighty could be good but for now direct call
});
gpaSlider.addEventListener('change', () => {
renderAll(); // Re-render all to propagate red status to next semester
});
// Populate List
const listContainer = clone.querySelector('.semester-list');
listContainer.id = `list-sem-${semId}`; // For drag logic ref
listContainer.setAttribute('ondrop', `drop(event, ${semId})`); // Update drop target ID
courses.forEach((c, idx) => {
const el = document.createElement('div');
el.className = 'course-card bg-white p-2 rounded border border-stone-200 shadow-sm flex justify-between items-center';
el.setAttribute('draggable', 'true');
el.setAttribute('ondragstart', `drag(event, ${semId}, ${idx})`);
// Styling for special items
if(c.name.includes('KONVERSI')) el.classList.add('bg-green-50', 'border-green-200');
el.innerHTML = `
<div class="overflow-hidden">
<div class="text-[10px] text-stone-400 font-mono">${c.code}</div>
<div class="text-xs font-bold text-stone-700 truncate w-32" title="${c.name}">${c.name}</div>
</div>
<div class="text-xs font-bold bg-stone-100 text-stone-600 px-1.5 py-0.5 rounded border border-stone-200 ml-2">${c.sks}</div>
`;
listContainer.appendChild(el);
});
container.innerHTML = '';
container.appendChild(clone);
}
function renderAll() {
// Render 1-4
renderSem(1, 'container-sem-1');
renderSem(2, 'container-sem-2');
renderSem(3, 'container-sem-3');
renderSem(4, 'container-sem-4');
// Render 5-8 (Inside Specialist Section)
const track = tracksBlueprint[currentTrack];
const contentDiv = document.getElementById('specialist-content');
// We recreate the grid structure for 5-8 inside the content div
// This is a bit brute force but keeps logic simple
contentDiv.innerHTML = `
<div class="mb-4">
<h3 class="text-xl font-bold ${track.color}">${track.name}</h3>
<p class="text-sm text-stone-500">${track.desc}</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div id="container-sem-5"></div>
<!-- MBKM Slot Visual -->
<div class="flex flex-col gap-4">
<div class="bg-gradient-to-br from-green-50 to-emerald-100 rounded-xl border border-green-200 p-3 shadow-sm h-auto relative">
<div class="absolute top-0 right-0 bg-green-200 text-green-800 text-[9px] font-bold px-2 py-1 rounded-bl">LIBURAN</div>
<h4 class="font-bold text-green-900 text-sm mb-2">MBKM (Sem Antara)</h4>
<div class="space-y-2">
${mbkmData.map(c => `
<div class="bg-white/60 p-2 rounded border border-green-100 flex justify-between items-center">
<span class="text-[10px] font-bold text-green-800">${c.name}</span>
<span class="text-[9px] bg-green-200 text-green-800 px-1 rounded">${c.sks}</span>
</div>
`).join('')}
</div>
</div>
<div id="container-sem-6" class="flex-grow"></div>
</div>
<div id="container-sem-7"></div>
<div id="container-sem-8"></div>
</div>
`;
renderSem(5, 'container-sem-5');
renderSem(6, 'container-sem-6');
renderSem(7, 'container-sem-7');
renderSem(8, 'container-sem-8');
updateCharts();
}
function switchTrack(key) {
document.querySelectorAll('.track-btn').forEach(b => b.classList.remove('active'));
document.getElementById(`btn-${key}`).classList.add('active');
initPlan(key);
renderAll();
}
// --- DRAG & DROP ---
let draggedItem = null;
function drag(ev, semId, idx) {
draggedItem = { semId, idx };
ev.dataTransfer.effectAllowed = "move";
ev.target.style.opacity = '0.4';
}
function allowDrop(ev) { ev.preventDefault(); }
function dragEnter(ev) {
ev.preventDefault();
ev.currentTarget.classList.add('drag-over');
}
function dragLeave(ev) {
ev.currentTarget.classList.remove('drag-over');
}
function drop(ev, targetSem) {
ev.preventDefault();
document.querySelectorAll('.semester-list').forEach(el => el.classList.remove('drag-over'));
if (!draggedItem) return;
const { semId, idx } = draggedItem;
if (semId !== targetSem) {
// Move logic
const course = currentPlan[semId][idx];
currentPlan[semId].splice(idx, 1);
currentPlan[targetSem].push(course);
renderAll(); // Re-calc status
} else {
renderAll(); // Just reset opacity
}
draggedItem = null;
}
// --- CHARTS ---
let sksChart = null;
function updateCharts() {
// Calc SKS
let dataSKS = [];
let bgColors = [];
let totalSKS = 0;
for(let i=1; i<=8; i++) {
const s = currentPlan[i].reduce((a,b)=>a+b.sks, 0);
dataSKS.push(s);
totalSKS += s;
// Color logic: Red if overload
let limit = 24;
if(i > 1) limit = calculateMaxSKS(semesterStats[i-1].gpa);
if (s > limit) bgColors.push('#ef4444');
else bgColors.push('#d6d3d1');
}
// Update Total Display
document.getElementById('grand-total-sks').textContent = totalSKS;
const statusEl = document.getElementById('graduation-status');
if(totalSKS >= 144) {
statusEl.textContent = "Siap Lulus";
statusEl.className = "mt-4 px-4 py-2 rounded-full text-sm font-bold bg-green-100 text-green-700";
} else {
statusEl.textContent = `Kurang ${144 - totalSKS} SKS`;
statusEl.className = "mt-4 px-4 py-2 rounded-full text-sm font-bold bg-red-100 text-red-700";
}
// Chart
const ctx = document.getElementById('sksChart').getContext('2d');
if(sksChart) sksChart.destroy();
sksChart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7', 'S8'],
datasets: [{
label: 'SKS',
data: dataSKS,
backgroundColor: bgColors,
borderRadius: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: { y: { beginAtZero: true, max: 28 } },
plugins: { legend: { display: false } }
}
});
}
// Init
window.addEventListener('DOMContentLoaded', () => {
switchTrack('ai');
});
</script>
</body>
</html>