Update simulasi-mk.html

This commit is contained in:
izu
2026-01-25 18:51:56 +00:00
parent 55699a4245
commit 531ab16e70

View File

@@ -25,8 +25,13 @@
.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; }
.semester-list { min-height: 120px; transition: background-color 0.2s, opacity 0.2s; }
/* Valid Drop Target */
.semester-list.valid-target { background-color: #f0fdf4; border: 2px dashed #4ade80; }
/* Invalid Drop Target (Wrong Parity) */
.semester-list.invalid-target { opacity: 0.3; background-color: #e5e5e5; pointer-events: none; border: 2px solid #d4d4d4; }
/* Warning States */
.semester-container { transition: all 0.3s; }
@@ -54,11 +59,10 @@
</head>
<body class="bg-stone-50 text-stone-800">
<!-- Chosen Palette: Warm Neutrals (Stone) with Logic-based Alerts (Red/Green) -->
<!-- Chosen Palette: Warm Neutrals with Logic Alerts -->
<!-- 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.
- Updated Drag & Drop Logic: Enforce Odd/Even semester constraints.
- Visual Feedback: Grey out invalid semesters when dragging.
-->
<!-- CONFIRMATION: NO SVG graphics used. NO Mermaid JS used. -->
@@ -68,7 +72,9 @@
<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).
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.
<br>
<span class="text-sm bg-red-100 text-red-800 px-2 py-1 rounded mt-2 inline-block font-bold">Aturan Baru: MK Ganjil hanya bisa ke Sem Ganjil, Genap ke Genap.</span>
</p>
<div class="mt-6 flex flex-wrap justify-center gap-4 text-sm">
@@ -169,7 +175,7 @@
</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)">
<div class="semester-list p-3 space-y-2 flex-grow relative" ondrop="drop(event)" ondragover="allowDrop(event)" ondragenter="dragEnter(event)" ondragleave="dragLeave(event)">
<!-- Course Cards go here -->
</div>
@@ -181,45 +187,46 @@
<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}
{code: "MK06", name: "Dasar Pemrograman*", sks: 3, priority: "core"},
{code: "MK07", name: "Algoritma & Logika", sks: 2, priority: "core"},
{code: "MK04", name: "Kalkulus", sks: 3, priority: "core"},
{code: "MK05", name: "Pengantar Informatika", sks: 2, priority: "standard"},
{code: "MK01", name: "Pendidikan Pancasila", sks: 2, priority: "standard"},
{code: "MK02", name: "Pendidikan Agama", sks: 3, priority: "standard"},
{code: "MK03", name: "Bahasa Inggris", sks: 2, priority: "support"},
{code: "MK08", name: "Kom. Profesional", sks: 2, priority: "support"}
],
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}
{code: "MK15", name: "Struktur Data & Algo*", sks: 3, priority: "core"},
{code: "MK12", name: "Matematika Diskrit", sks: 3, priority: "core"},
{code: "MK11", name: "Sistem Digital", sks: 2, priority: "core"},
{code: "MK13", name: "Org. & Ars. Komputer", sks: 3, priority: "core"},
{code: "MK14", name: "Sistem Operasi*", sks: 3, priority: "core"},
{code: "MK16", name: "Literasi Digital", sks: 2, priority: "support"},
{code: "MK10", name: "Bahasa Indonesia", sks: 2, priority: "support"},
{code: "MK09", name: "Kewarganegaraan", sks: 2, priority: "support"}
],
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}
{code: "MK20", name: "Basis Data*", sks: 3, priority: "core"},
{code: "MK21", name: "Jaringan Komputer*", sks: 3, priority: "core"},
{code: "MK27", name: "PBO*", sks: 3, priority: "core"},
{code: "MK22", name: "Strategi Algoritma*", sks: 3, priority: "core"},
{code: "MK17", name: "Aljabar Linier", sks: 3, priority: "core"},
{code: "MK24", name: "IMK", sks: 2, priority: "support"},
{code: "MK19", name: "Sistem Informasi", sks: 2, priority: "support"},
{code: "MK18", name: "Etika & Keberlanjutan", sks: 2, priority: "support"}
],
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}
{code: "MK23", name: "RPL*", sks: 3, priority: "core"},
{code: "MK32", name: "Probstat", sks: 3, priority: "core"},
{code: "MK31", name: "Pemrograman Web*", sks: 3, priority: "core"},
{code: "MK30", name: "Kecerdasan Buatan", sks: 3, priority: "core"},
{code: "MK25", name: "APSI", sks: 3, priority: "standard"},
{code: "MK29", name: "Pemrog. Jaringan*", sks: 3, priority: "standard"},
{code: "MK26", name: "Sis. Paralel", sks: 2, priority: "standard"}
]
};
@@ -229,31 +236,31 @@
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}
{code: "MK35", name: "Machine Learning", sks: 3, priority: "core"},
{code: "MK28", name: "Metode Numerik*", sks: 3, priority: "core"},
{code: "MK36", name: "Proyek PL*", sks: 3, priority: "core"},
{code: "MK37", name: "Proposal TA", sks: 2, priority: "core"},
{code: "MKP", name: "Data Mining", sks: 3, priority: "standard"},
{code: "MKP", name: "Big Data", sks: 3, priority: "standard"},
{code: "MK33", name: "IoT", sks: 3, priority: "standard"}
],
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}
{code: "MK38", name: "Comp. Vision*", sks: 3, priority: "core"},
{code: "MK43", name: "NLP", sks: 3, priority: "core"},
{code: "MK44", name: "Kerja Praktik", sks: 2, priority: "core"},
{code: "MKP", name: "Deep Learning", sks: 3, priority: "standard"},
{code: "MKP", name: "AI Ethics", sks: 2, priority: "standard"},
{code: "MKP", name: "Pilihan Lintas KK", sks: 3, priority: "standard"},
{code: "MK42", name: "Keamanan Info*", sks: 2, priority: "standard"}
],
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}
{code: "MK48", name: "TA 1", sks: 2, priority: "core"},
{code: "MK45", name: "Teknopreneur [K]", sks: 3, priority: "standard"},
{code: "MK47", name: "Uji PL", sks: 3, priority: "standard"},
{code: "MK42", name: "Keamanan Info", sks: 3, priority: "standard"},
{code: "MKP", name: "Health Info", sks: 3, priority: "standard"},
{code: "MKP", name: "Reinforcement L.", sks: 3, priority: "standard"},
{code: "MKP", name: "Quantum/Bebas", sks: 5, priority: "standard"}
]
},
rpl: {
@@ -261,30 +268,30 @@
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}
{code: "MK36", name: "Proyek PL*", sks: 3, priority: "core"},
{code: "MKP", name: "UX/UI Design", sks: 3, priority: "standard"},
{code: "MKP", name: "Secure SE", sks: 3, priority: "standard"},
{code: "MKP", name: "Web Lanjut", sks: 3, priority: "standard"},
{code: "MK28", name: "Metode Numerik*", sks: 3, priority: "standard"},
{code: "MK37", name: "Proposal TA", sks: 2, priority: "core"},
{code: "MK33", name: "IoT", sks: 3, priority: "standard"}
],
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}
{code: "MK39", name: "Pemrog. Mobile*", sks: 3, priority: "core"},
{code: "MKP", name: "Microservices", sks: 3, priority: "standard"},
{code: "MKP", name: "Game Dev", sks: 3, priority: "standard"},
{code: "MK44", name: "Kerja Praktik", sks: 2, priority: "core"},
{code: "MK41", name: "Sis. Enterprise", sks: 3, priority: "standard"},
{code: "MKP", name: "Pil. Lintas KK", sks: 4, priority: "standard"}
],
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}
{code: "MK48", name: "TA 1", sks: 2, priority: "core"},
{code: "MK45", name: "Teknopreneur [K]", sks: 3, priority: "standard"},
{code: "MK47", name: "Uji PL", sks: 3, priority: "standard"},
{code: "MK42", name: "Keamanan Info", sks: 3, priority: "standard"},
{code: "MKP", name: "DevOps", sks: 3, priority: "standard"},
{code: "MKP", name: "Soft. Arch", sks: 3, priority: "standard"},
{code: "MKP", name: "Pil. Bebas", sks: 5, priority: "standard"}
]
},
net: {
@@ -292,30 +299,30 @@
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}
{code: "MK33", name: "IoT", sks: 3, priority: "core"},
{code: "MK34", name: "Manajemen Jar.*", sks: 3, priority: "core"},
{code: "MKP", name: "Embedded Sys", sks: 3, priority: "standard"},
{code: "MKP", name: "Ethical Hack", sks: 3, priority: "standard"},
{code: "MK36", name: "Proyek PL*", sks: 3, priority: "core"},
{code: "MK28", name: "Metode Numerik*", sks: 3, priority: "standard"},
{code: "MK37", name: "Proposal TA", sks: 2, priority: "core"}
],
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}
{code: "MK42", name: "Keamanan Info*", sks: 3, priority: "core"},
{code: "MKP", name: "Wireless Sensor", sks: 3, priority: "standard"},
{code: "MKP", name: "Cryptography", sks: 3, priority: "standard"},
{code: "MK44", name: "Kerja Praktik", sks: 2, priority: "core"},
{code: "MK39", name: "Mobile (IoT)*", sks: 3, priority: "standard"},
{code: "MKP", name: "Pil. Lintas KK", sks: 3, priority: "standard"}
],
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}
{code: "MK48", name: "TA 1", sks: 2, priority: "core"},
{code: "MK45", name: "Teknopreneur [K]", sks: 3, priority: "standard"},
{code: "MK47", name: "Uji PL", sks: 3, priority: "standard"},
{code: "MKP", name: "Cloud IoT", sks: 3, priority: "standard"},
{code: "MKP", name: "Blockchain", sks: 3, priority: "standard"},
{code: "MKP", name: "Pil. Bebas 1", sks: 3, priority: "standard"},
{code: "MKP", name: "Pil. Bebas 2", sks: 6, priority: "standard"}
]
},
si: {
@@ -323,38 +330,38 @@
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}
{code: "MKP", name: "Business Intel", sks: 3, priority: "standard"},
{code: "MKP", name: "GIS II", sks: 3, priority: "standard"},
{code: "MK36", name: "Proyek PL*", sks: 3, priority: "core"},
{code: "MK28", name: "Metode Numerik*", sks: 3, priority: "standard"},
{code: "MK37", name: "Proposal TA", sks: 2, priority: "core"},
{code: "MKP", name: "E-Business", sks: 3, priority: "standard"},
{code: "MK33", name: "IoT", sks: 3, priority: "standard"}
],
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}
{code: "MK41", name: "Sis. Enterprise", sks: 3, priority: "core"},
{code: "MK40", name: "SIG", sks: 3, priority: "core"},
{code: "MKP", name: "Enterprise Arch", sks: 3, priority: "standard"},
{code: "MKP", name: "Smart City", sks: 3, priority: "standard"},
{code: "MK44", name: "Kerja Praktik", sks: 2, priority: "core"},
{code: "MKP", name: "Pil. Lintas KK", sks: 3, priority: "standard"}
],
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}
{code: "MK48", name: "TA 1", sks: 2, priority: "core"},
{code: "MK45", name: "Teknopreneur [K]", sks: 3, priority: "standard"},
{code: "MK47", name: "Uji PL", sks: 3, priority: "standard"},
{code: "MK42", name: "Keamanan Info", sks: 3, priority: "standard"},
{code: "MKP", name: "SCM", sks: 3, priority: "standard"},
{code: "MKP", name: "IT Governance", sks: 3, priority: "standard"},
{code: "MKP", name: "Pil. Bebas", sks: 6, priority: "standard"}
]
}
};
const mbkmData = [
{code: "MK46", name: "PMKM (Wajib)", sks: 2},
{code: "MK45", name: "Teknopreneur (Konversi)", sks: 3},
{code: "MKP", name: "Pilihan Bebas (Ops)", sks: 3}
{code: "MK46", name: "PMKM (Wajib)", sks: 2, priority: "core"},
{code: "MK45", name: "Teknopreneur (Konversi)", sks: 3, priority: "standard"},
{code: "MKP", name: "Pilihan Bebas (Ops)", sks: 3, priority: "standard"}
];
// --- STATE ---
@@ -391,7 +398,7 @@
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}];
currentPlan[8] = [{code: "MK49", name: "TA 2 (Sidang)", sks: 4, priority: "core"}];
}
function renderSem(semId, containerId) {
@@ -451,21 +458,24 @@
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
renderAll();
});
// 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
listContainer.id = `list-sem-${semId}`;
// --- NEW: Parity Enforcement Logic ---
const isOddSem = semId % 2 !== 0;
const parityClass = isOddSem ? 'odd-semester' : 'even-semester';
listContainer.classList.add(parityClass);
listContainer.dataset.parity = isOddSem ? 'odd' : 'even';
// -------------------------------------
listContainer.setAttribute('ondrop', `drop(event, ${semId})`);
courses.forEach((c, idx) => {
const el = document.createElement('div');
@@ -473,12 +483,24 @@
el.setAttribute('draggable', 'true');
el.setAttribute('ondragstart', `drag(event, ${semId}, ${idx})`);
// Styling for special items
// Styling
if(c.name.includes('KONVERSI')) el.classList.add('bg-green-50', 'border-green-200');
let badgeHTML = '';
if(c.priority === 'core') {
el.classList.add('border-l-4', 'border-l-blue-500');
badgeHTML = '<span class="text-[9px] bg-blue-100 text-blue-700 px-1 rounded ml-1 border border-blue-200" title="Pondasi Wajib">PONDASI</span>';
} else if(c.priority === 'support') {
el.classList.add('bg-stone-50', 'opacity-90');
badgeHTML = '<span class="text-[9px] bg-stone-200 text-stone-600 px-1 rounded ml-1 border border-stone-300" title="Bisa Ditunda">TUNDA?</span>';
}
el.innerHTML = `
<div class="overflow-hidden">
<div class="text-[10px] text-stone-400 font-mono">${c.code}</div>
<div class="flex items-center gap-1">
<div class="text-[10px] text-stone-400 font-mono">${c.code}</div>
${badgeHTML}
</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>
@@ -491,18 +513,14 @@
}
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>
@@ -510,7 +528,6 @@
</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>
@@ -518,7 +535,10 @@
<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>
<div class="flex flex-col">
<span class="text-[10px] font-bold text-green-800">${c.name}</span>
<span class="text-[8px] text-green-600">${c.priority === 'core' ? 'Wajib' : 'Opsional'}</span>
</div>
<span class="text-[9px] bg-green-200 text-green-800 px-1 rounded">${c.sks}</span>
</div>
`).join('')}
@@ -546,30 +566,85 @@
renderAll();
}
// --- DRAG & DROP ---
// --- DRAG & DROP WITH PARITY CHECK ---
let draggedItem = null;
function drag(ev, semId, idx) {
draggedItem = { semId, idx };
ev.dataTransfer.effectAllowed = "move";
ev.target.style.opacity = '0.4';
// Set global parity state for visual feedback
const isOddSource = semId % 2 !== 0;
document.body.classList.add(isOddSource ? 'dragging-odd' : 'dragging-even');
// Visual Update: Dim invalid targets
document.querySelectorAll('.semester-list').forEach(el => {
const targetIsOdd = el.dataset.parity === 'odd';
if(isOddSource !== targetIsOdd) {
el.classList.add('invalid-target');
} else {
el.classList.add('valid-target-hint'); // Optional hint style
}
});
}
function dragEndCleanup() {
document.body.classList.remove('dragging-odd', 'dragging-even');
document.querySelectorAll('.semester-list').forEach(el => {
el.classList.remove('drag-over', 'valid-target', 'invalid-target', 'valid-target-hint');
});
if(draggedItem) {
// Reset opacity if drag failed/cancelled
// Ideally found by element ref, but re-render fixes it anyway
}
draggedItem = null;
}
function allowDrop(ev) {
ev.preventDefault();
}
function allowDrop(ev) { ev.preventDefault(); }
function dragEnter(ev) {
ev.preventDefault();
ev.currentTarget.classList.add('drag-over');
const target = ev.currentTarget;
// Check parity match visual
if(!draggedItem) return;
const sourceParity = draggedItem.semId % 2 !== 0 ? 'odd' : 'even';
const targetParity = target.dataset.parity;
if(sourceParity === targetParity) {
target.classList.add('valid-target');
}
}
function dragLeave(ev) {
ev.currentTarget.classList.remove('drag-over');
ev.currentTarget.classList.remove('valid-target');
}
function drop(ev, targetSem) {
ev.preventDefault();
document.querySelectorAll('.semester-list').forEach(el => el.classList.remove('drag-over'));
if (!draggedItem) return;
if (!draggedItem) {
dragEndCleanup();
return;
}
const { semId, idx } = draggedItem;
// --- VALIDATION: PARITY CHECK ---
const sourceParity = semId % 2;
const targetParity = targetSem % 2;
if (sourceParity !== targetParity) {
// Invalid Drop
// alert("Mata kuliah Ganjil hanya bisa diambil di Semester Ganjil, dan Genap di Genap.");
dragEndCleanup();
renderAll(); // Reset visual state
return;
}
// --------------------------------
if (semId !== targetSem) {
// Move logic
@@ -577,12 +652,16 @@
currentPlan[semId].splice(idx, 1);
currentPlan[targetSem].push(course);
renderAll(); // Re-calc status
dragEndCleanup();
renderAll();
} else {
renderAll(); // Just reset opacity
dragEndCleanup();
renderAll();
}
draggedItem = null;
}
// Add global dragend listener to ensure cleanup happens even if dropped outside
document.addEventListener('dragend', dragEndCleanup);
// --- CHARTS ---
let sksChart = null;