Update simulasi-mk.html

versi 7
This commit is contained in:
izu
2026-02-04 02:40:48 +00:00
parent caeb71803c
commit 258ecec880

View File

@@ -25,8 +25,8 @@
.course-card.violation .cat-bar { background-color: #ef4444 !important; }
.violation-badge { position: absolute; right: 0.5rem; bottom: 0.5rem; font-size: 0.65rem; color: #dc2626; font-weight: bold; display: flex; align-items: center; gap: 2px; }
/* Elective Styles (New) */
.course-card.is-elective { background-color: #fffbeb; border-color: #fcd34d; } /* Yellow tint */
/* Elective Styles */
.course-card.is-elective { background-color: #fffbeb; border-color: #fcd34d; }
/* Grade Badges */
.grade-badge { position: absolute; right: 0.5rem; top: 0.5rem; font-size: 0.7rem; font-weight: bold; padding: 0.1rem 0.4rem; border-radius: 0.25rem; }
@@ -134,7 +134,7 @@
<h1 class="text-3xl md:text-4xl font-bold text-slate-900 mb-2">Simulasi Studi Berbasis IPS</h1>
<p class="text-slate-600 max-w-2xl mx-auto text-sm md:text-base">
Simulasi nyata: <strong>IPS Semester lalu menentukan jatah SKS semester depan.</strong><br>
Jalur <strong>UMUM (Campuran)</strong> sekarang menampilkan mata kuliah spesifik di semester akhir. Buka Katalog untuk mengganti sesuai minat Anda.
Jika IPS rendah, Anda <strong>wajib mengurangi SKS</strong> di semester berikutnya.
</p>
<div class="mt-6 flex flex-wrap justify-center gap-3 text-xs font-semibold opacity-80">
@@ -200,7 +200,7 @@
</div>
<script>
// --- UTILS: Fix for HTTP Environments (Coolify) ---
// --- UTILS: Fix for HTTP Environments ---
function generateUUID() {
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
return crypto.randomUUID();
@@ -224,7 +224,6 @@
RES: { color: 'bg-red-500' }
};
// --- Prerequisite Rules ---
const prerequisites = {
"M14": { reqId: "M7", reqName: "Logika Komputasional" },
"M19": { reqId: "M14", reqName: "Dasar Pemrograman" },
@@ -300,13 +299,12 @@
};
const electives = {
// General track now has specific courses as a "Sample Mix"
general: [
{ id: "GEN1", name: "Cross-Platform Dev", sks: 3, cat: "EL" }, // RPL flavor
{ id: "GEN2", name: "Data Mining", sks: 3, cat: "EL" }, // AI flavor
{ id: "GEN3", name: "Cloud Computing", sks: 3, cat: "EL" }, // Net flavor
{ id: "GEN4", name: "E-Business", sks: 3, cat: "EL" }, // SI flavor
{ id: "GEN5", name: "Blockchain Tech", sks: 3, cat: "EL" } // Advanced
{ id: "GEN1", name: "Cross-Platform Dev", sks: 3, cat: "EL" },
{ id: "GEN2", name: "Data Mining", sks: 3, cat: "EL" },
{ id: "GEN3", name: "Cloud Computing", sks: 3, cat: "EL" },
{ id: "GEN4", name: "E-Business", sks: 3, cat: "EL" },
{ id: "GEN5", name: "Blockchain Tech", sks: 3, cat: "EL" }
],
ai: [
{ id: "AI1", name: "Deep Learning", sks: 3, cat: "EL" },
@@ -349,7 +347,6 @@
currentTrackId = trackId;
currentPlan = {};
// Populate Plan
for(let i=1; i<=8; i++) {
currentPlan[i] = [];
if(baseCurriculum[i]) {
@@ -357,7 +354,6 @@
}
}
// Add Electives with Flag
const tr = electives[trackId] || electives['general'];
const distribute = [[5,0], [6,1], [7,2], [7,3], [8,4]];
distribute.forEach(([sem, idx]) => {
@@ -366,7 +362,7 @@
...tr[idx],
grade: 4,
uuid: generateUUID(),
isElective: true // Tag as elective
isElective: true
});
}
});
@@ -375,16 +371,13 @@
renderAll();
}
// --- Prerequisite Check Function ---
function checkPrerequisite(courseId, targetSem) {
const rule = prerequisites[courseId];
if (!rule) return { allowed: true };
let passed = false;
// Scan only semesters BEFORE the target semester
for (let i = 1; i < targetSem; i++) {
if (!currentPlan[i]) continue;
// Find instance of prerequisite course that is Passed (Grade > 0)
const found = currentPlan[i].find(c => c.id === rule.reqId && c.grade > 0);
if (found) {
passed = true;
@@ -398,22 +391,25 @@
return { allowed: true };
}
// --- Fixed Stat Calculations ---
function calculateSemStats(semId) {
const courses = currentPlan[semId] || [];
let totalSKS = 0;
let totalPoints = 0;
let loadSKS = 0;
let sksAttempted = 0; // Divisor IPS (Termasuk E)
let sksPassed = 0; // Total SKS Lulus (Grade > 0)
let totalPoints = 0; // Numerator IPS
courses.forEach(c => {
loadSKS += c.sks;
if(c.grade > 0) {
totalSKS += c.sks;
totalPoints += (c.sks * c.grade);
sksAttempted += c.sks;
totalPoints += (c.sks * c.grade); // Grade 0 * sks = 0
if (c.grade > 0) {
sksPassed += c.sks;
}
});
const ips = totalSKS > 0 ? (totalPoints / totalSKS) : 0;
return { ips, loadSKS };
// IPS = Total Points / SKS Attempted
const ips = sksAttempted > 0 ? (totalPoints / sksAttempted) : 0;
return { ips, sksAttempted, sksPassed, totalPoints };
}
function getMaxSKS(prevIPS) {
@@ -428,7 +424,7 @@
if (!grid) return;
grid.innerHTML = '';
// First Pass: Check Violations (Prerequisites)
// First Pass: Check Violations
for(let i=1; i<=8; i++) {
currentPlan[i].forEach(course => {
const check = checkPrerequisite(course.id, i);
@@ -448,7 +444,8 @@
}
const currentStats = calculateSemStats(i);
const currentLoad = currentStats.loadSKS;
// Use sksAttempted for load display
const currentLoad = currentStats.sksAttempted;
const isOverload = currentLoad > maxSKS;
const semDiv = document.createElement('div');
@@ -576,12 +573,38 @@
courseToAdd = currentPlan[dragSource.semId][dragSource.index];
}
// --- 1. Prerequisite Check ---
const check = checkPrerequisite(courseToAdd.id, targetSem);
if (!check.allowed) {
showToast("❌ " + check.msg);
return;
}
// --- 2. SKS Limit Check (NEW) ---
let maxSKS = 20; // Default for Sem 1
let prevIPS = 0;
if (targetSem > 1) {
// Get IPS from previous semester
const prevStats = calculateSemStats(targetSem - 1);
prevIPS = prevStats.ips;
maxSKS = getMaxSKS(prevIPS);
}
// Current load in target semester
const currentStats = calculateSemStats(targetSem);
const currentLoad = currentStats.sksAttempted;
// Check if adding this course exceeds limit
if (currentLoad + courseToAdd.sks > maxSKS) {
let msg = `⚠️ Gagal! Maksimal ${maxSKS} SKS.`;
if(targetSem > 1) {
msg += ` (IPS Sem ${targetSem-1}: ${prevIPS.toFixed(2)})`;
}
showToast(msg);
return;
}
if(dragSource.type === 'catalog') {
currentPlan[targetSem].push(dragSource.course);
} else {
@@ -768,9 +791,11 @@
let domainChart = null;
function updateGlobalStats() {
let totalSKS = 0;
let totalPoints = 0;
let failedSKS = 0;
let globalAttempted = 0;
let globalPassed = 0;
let globalPoints = 0;
let globalFailedSKS = 0;
let semIPS = [];
let catCounts = { MKWU:0, BSC:0, FND:0, CORE:0, ADV:0, PROF:0, EL:0 };
@@ -778,26 +803,28 @@
const stats = calculateSemStats(i);
semIPS.push(stats.ips);
globalAttempted += stats.sksAttempted;
globalPassed += stats.sksPassed;
globalPoints += stats.totalPoints;
currentPlan[i].forEach(c => {
if (c.grade === 0) globalFailedSKS += c.sks;
// Count categories for passed/active courses
if (c.grade > 0) {
totalSKS += c.sks;
totalPoints += (c.sks * c.grade);
const cat = c.cat === 'CAP' || c.cat === 'RES' ? 'PROF' : c.cat;
if(catCounts[cat] !== undefined) catCounts[cat] += c.sks;
} else {
failedSKS += c.sks;
}
});
}
const ipk = totalSKS > 0 ? (totalPoints / totalSKS) : 0;
document.getElementById('total-sks-passed').innerText = totalSKS;
const ipk = globalAttempted > 0 ? (globalPoints / globalAttempted) : 0;
document.getElementById('total-sks-passed').innerText = globalPassed;
document.getElementById('ipk-global').innerText = ipk.toFixed(2);
document.getElementById('total-fail').innerText = failedSKS;
document.getElementById('total-fail').innerText = globalFailedSKS;
let predikat = "Memuaskan";
// NEW LOGIC
const adaNilaiE = failedSKS > 0;
const adaNilaiE = globalFailedSKS > 0;
if (ipk >= 3.51 && !adaNilaiE) {
predikat = "Cum Laude";